Compare commits
31 Commits
6c96e92fa5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dfae1f136b | |||
|
|
74e48aa1e5 | ||
| a6ac6ce355 | |||
|
|
9832121dbb | ||
| d0a5925652 | |||
|
|
e91c4de7ed | ||
| 51ab88b1f8 | |||
|
|
709744d533 | ||
|
|
ddd8784b56 | ||
| 226a823c68 | |||
|
|
67befa56df | ||
|
|
13df66d137 | ||
| 541a17c792 | |||
|
|
9fa613b8c1 | ||
| 8f61d5fb61 | |||
|
|
09e1f94b7d | ||
| 8ceb38c3dd | |||
|
|
a169d2a452 | ||
|
|
20743e9b54 | ||
| 75f97778f8 | |||
|
|
18475fa731 | ||
| 04a6f6eabb | |||
|
|
7e99d7e304 | ||
| 731ae88adf | |||
|
|
fce4b77bd6 | ||
| 3538f43267 | |||
|
|
7eec584a66 | ||
| c154e70495 | |||
|
|
e6c292da25 | ||
| bd215cba54 | |||
|
|
30a168e853 |
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Keep binary assets verbatim (no EOL/charset normalization on checkout).
|
||||
*.sys binary
|
||||
*.cat binary
|
||||
*.exe binary
|
||||
*.dll binary
|
||||
*.iso binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
*.cab binary
|
||||
windows/drivers/** binary
|
||||
@@ -40,6 +40,22 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Free disk space
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Each successful build persists a ~5 GB ISO to C:\silvermetal\out, and the
|
||||
# boot.wim now carries WinPE-NetFx/PowerShell (bigger image). On the single-volume
|
||||
# runner that accumulation starves oscdimg ("Insufficient disk space"). Clear the
|
||||
# prior build output + any stale work dir before building so space self-heals.
|
||||
$before = [math]::Round((Get-PSDrive C).Free/1GB,1)
|
||||
Remove-Item 'C:\silvermetal\out\*' -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Get-ChildItem 'C:\gitea-runner\workspace' -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$t = Join-Path $_.FullName 'tmp\smbuild'
|
||||
if (Test-Path $t) { Remove-Item $t -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
$after = [math]::Round((Get-PSDrive C).Free/1GB,1)
|
||||
Write-Host " C: free ${before}GB -> ${after}GB (cleared prior ISO output + stale smbuild)"
|
||||
|
||||
- name: Ensure Windows ADK (oscdimg)
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -58,6 +74,28 @@ jobs:
|
||||
}
|
||||
if (-not (Test-Path $deploy)) { throw 'ADK Deployment Tools install failed.' }
|
||||
|
||||
- name: Ensure Windows ADK WinPE add-on
|
||||
shell: pwsh
|
||||
run: |
|
||||
# build.ps1 (Invoke-ForceLegacySetup) calls Add-WindowsPackage with the
|
||||
# WinPE_OCs cabs, which only exist if the ADK WinPE add-on is installed.
|
||||
$ocs = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs'
|
||||
if (Test-Path $ocs) {
|
||||
Write-Host 'ADK WinPE add-on (WinPE_OCs) already present.'; exit 0
|
||||
}
|
||||
Write-Host 'Installing ADK WinPE add-on...'
|
||||
# Prefer winget; fall back to the WinPE add-on web installer.
|
||||
$ok = $false
|
||||
try { winget install --id Microsoft.ADKPEAddon -e --accept-source-agreements --accept-package-agreements --silent; $ok = $true } catch {}
|
||||
if (-not $ok) {
|
||||
# NOTE: fwlink id is ADK-version-specific; update if the channel rolls.
|
||||
Invoke-WebRequest 'https://go.microsoft.com/fwlink/?linkid=2289981' -OutFile "$env:TEMP\adkwinpesetup.exe"
|
||||
Start-Process "$env:TEMP\adkwinpesetup.exe" -ArgumentList '/quiet','/norestart','/features','OptionId.WindowsPreinstallationEnvironment' -Wait
|
||||
}
|
||||
# The WinPE collector is a required, core feature of this image, so a missing
|
||||
# WinPE_OCs dir is a hard build gate (fail fast with a clear message).
|
||||
if (-not (Test-Path $ocs)) { throw 'ADK WinPE add-on install failed (WinPE_OCs missing)' }
|
||||
|
||||
- name: Setup .NET 9 SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
@@ -146,6 +184,15 @@ jobs:
|
||||
# RUNNER_TEMP is per-job/ephemeral. Keep the latest validated build at a
|
||||
# stable path so it can be retrieved (e.g. for VM boot-testing) out of band.
|
||||
New-Item -ItemType Directory -Force 'C:\silvermetal\out' | Out-Null
|
||||
# The ISO is already built + validated; free the build working set (extracted ISO
|
||||
# tree + the mounted/expanded install.wim + the 5GB base ISO) BEFORE the ~5GB persist
|
||||
# copy, or the single-volume runner runs out of space mid-copy. The ISO itself lives
|
||||
# in RUNNER_TEMP\out (untouched) and the SBOM/SHA uploads read from there too.
|
||||
$before = [math]::Round((Get-PSDrive C).Free/1GB,1)
|
||||
Remove-Item "$env:RUNNER_TEMP\smbuild" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$env:RUNNER_TEMP\base.iso" -Force -ErrorAction SilentlyContinue
|
||||
$after = [math]::Round((Get-PSDrive C).Free/1GB,1)
|
||||
Write-Host " freed build working set: C: ${before}GB -> ${after}GB before persist"
|
||||
Copy-Item "$env:RUNNER_TEMP\out\*" 'C:\silvermetal\out\' -Force
|
||||
Get-ChildItem 'C:\silvermetal\out' | ForEach-Object { Write-Host $_.Name }
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -68,3 +68,7 @@ coverage/
|
||||
|
||||
# SBOM intermediates (final SBOMs are committed; intermediates are not)
|
||||
sbom/work/
|
||||
|
||||
# Driver binaries (e.g. virtio NetKVM netkvmp.exe) must be tracked despite the global
|
||||
# *.exe / *.msi ignores above -- they are referenced by the .inf and DISM needs them.
|
||||
!windows/drivers/**
|
||||
|
||||
@@ -1,5 +1,68 @@
|
||||
#Requires -Version 5.1
|
||||
$ErrorActionPreference='SilentlyContinue'
|
||||
# Register the inbox App Installer if present, else nothing to do (offline image w/o it).
|
||||
# Provision winget (the App Installer) when absent. Windows IoT Enterprise LTSC
|
||||
# ships WITHOUT the inbox Microsoft.DesktopAppInstaller package, so re-registering
|
||||
# it is not enough - we download and install it (plus dependencies) online at apply
|
||||
# time. Best-effort and idempotent: exit 0 if winget ends up available, else 1.
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
|
||||
function Test-Winget {
|
||||
return [bool](Get-Command winget -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
# Fast path 1: winget already on PATH.
|
||||
if (Test-Winget) { exit 0 }
|
||||
|
||||
# Fast path 2: an inbox App Installer package is present - just re-register it.
|
||||
Get-AppxPackage -AllUsers Microsoft.DesktopAppInstaller |
|
||||
ForEach-Object { Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml" }
|
||||
ForEach-Object { Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml" }
|
||||
if (Test-Winget) { exit 0 }
|
||||
|
||||
# Slow path: download + install the App Installer and its dependencies online.
|
||||
$temp = $env:TEMP
|
||||
$bundlePath = Join-Path $temp 'Microsoft.DesktopAppInstaller.msixbundle'
|
||||
$vclibsPath = Join-Path $temp 'Microsoft.VCLibs.x64.14.00.Desktop.appx'
|
||||
$uixamlNupkg = Join-Path $temp 'microsoft.ui.xaml.2.8.6.nupkg'
|
||||
$uixamlExtract = Join-Path $temp 'uixaml.2.8.6'
|
||||
$uixamlAppx = Join-Path $uixamlExtract 'tools\AppX\x64\Release\Microsoft.UI.Xaml.2.8.appx'
|
||||
|
||||
$bundleUrl = 'https://aka.ms/getwinget'
|
||||
$vclibsUrl = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx'
|
||||
$uixamlUrl = 'https://globalcdn.nuget.org/packages/microsoft.ui.xaml.2.8.6.nupkg'
|
||||
|
||||
function Get-File {
|
||||
param([string]$Url, [string]$Destination)
|
||||
try {
|
||||
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing
|
||||
return (Test-Path $Destination)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Download the App Installer bundle (required).
|
||||
if (-not (Get-File -Url $bundleUrl -Destination $bundlePath)) { exit 1 }
|
||||
|
||||
# Download the VCLibs desktop dependency (required).
|
||||
if (-not (Get-File -Url $vclibsUrl -Destination $vclibsPath)) { exit 1 }
|
||||
|
||||
# Download the UI.Xaml 2.8 nuget package (a .zip) and extract the appx from it.
|
||||
if (-not (Get-File -Url $uixamlUrl -Destination $uixamlNupkg)) { exit 1 }
|
||||
try {
|
||||
if (Test-Path $uixamlExtract) { Remove-Item -Path $uixamlExtract -Recurse -Force }
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory($uixamlNupkg, $uixamlExtract)
|
||||
} catch {
|
||||
exit 1
|
||||
}
|
||||
if (-not (Test-Path $uixamlAppx)) { exit 1 }
|
||||
|
||||
# Install order: VCLibs dependency, then UI.Xaml dependency, then the bundle with
|
||||
# both supplied as DependencyPath. Per-user Add-AppxPackage (toolbox runs as the
|
||||
# real admin user at first logon).
|
||||
Add-AppxPackage -Path $vclibsPath
|
||||
Add-AppxPackage -Path $uixamlAppx
|
||||
Add-AppxPackage -Path $bundlePath -DependencyPath $vclibsPath, $uixamlAppx
|
||||
|
||||
# Final re-check.
|
||||
if (Test-Winget) { exit 0 }
|
||||
exit 1
|
||||
|
||||
@@ -176,13 +176,13 @@ try {
|
||||
$btnCancel = New-Object Windows.Forms.Button
|
||||
$btnCancel.Text = 'Cancel'
|
||||
Style-Button $btnCancel $false
|
||||
$btnCancel.Location = New-Object Drawing.Point(580, ($y + 48))
|
||||
$btnCancel.Location = New-Object Drawing.Point(580, ($y + 120))
|
||||
$form.Controls.Add($btnCancel)
|
||||
|
||||
$btnFinish = New-Object Windows.Forms.Button
|
||||
$btnFinish.Text = 'Finish'
|
||||
Style-Button $btnFinish $true
|
||||
$btnFinish.Location = New-Object Drawing.Point(750, ($y + 48))
|
||||
$btnFinish.Location = New-Object Drawing.Point(750, ($y + 120))
|
||||
$form.Controls.Add($btnFinish)
|
||||
|
||||
# --- behaviour -------------------------------------------------------------
|
||||
@@ -250,6 +250,9 @@ try {
|
||||
[Environment]::Exit(1)
|
||||
}
|
||||
catch {
|
||||
# Any failure -> exit 1 so Start-Collector.cmd falls back to the default answer file.
|
||||
# Any failure (e.g. WinForms can't load in WinPE) -> log it where Start-Collector.cmd
|
||||
# can show it on the console, then exit 1 so the wrapper falls back to the default answer file.
|
||||
try { ($_ | Out-String) | Set-Content 'X:\sm\collector-error.txt' -Encoding ASCII } catch {}
|
||||
Write-Host ('Collector error: ' + ($_ | Out-String))
|
||||
[Environment]::Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#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.
|
||||
# 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(
|
||||
@@ -29,7 +32,45 @@ function New-SmAnswerFile {
|
||||
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')) """
|
||||
# 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(" <SynchronousCommand wcm:action=""add"" xmlns:wcm=""http://schemas.microsoft.com/WMIConfig/2002/State"">")
|
||||
[void]$firstLogonSb.AppendLine(" <Order>$order</Order>")
|
||||
[void]$firstLogonSb.AppendLine(" <CommandLine>$(EscContent $cmd)</CommandLine>")
|
||||
[void]$firstLogonSb.AppendLine(" </SynchronousCommand>")
|
||||
}
|
||||
$firstLogonCommands = $firstLogonSb.ToString().TrimEnd()
|
||||
|
||||
@"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -64,17 +105,6 @@ function New-SmAnswerFile {
|
||||
<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>
|
||||
@@ -91,11 +121,7 @@ function New-SmAnswerFile {
|
||||
<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
|
||||
</FirstLogonCommands>
|
||||
<RegisteredOwner>SilverMetal</RegisteredOwner><RegisteredOrganization>SilverLABS</RegisteredOrganization>
|
||||
</component>
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
@echo off
|
||||
set "SETUP=X:\sources\setup.exe"
|
||||
if not exist "%SETUP%" set "SETUP=X:\setup.exe"
|
||||
REM WinPE entry point. SM_UNATTENDED=1 -> skip the UI and launch Setup with the default
|
||||
REM answer file (used by CI / non-interactive builds).
|
||||
REM WinPE entry point (launched via Setup\CmdLine). SM_UNATTENDED=1 -> skip the UI and
|
||||
REM launch Setup with the default answer file (used by CI / non-interactive builds).
|
||||
if "%SM_UNATTENDED%"=="1" (
|
||||
start /wait "%SETUP%" /unattend:X:\autounattend.xml
|
||||
exit /b 0
|
||||
)
|
||||
REM Initialise WinPE (a Setup\CmdLine launch can bypass the normal startnet/wpeinit).
|
||||
wpeinit
|
||||
echo ============================================
|
||||
echo SilverMetal pre-config collector
|
||||
echo ============================================
|
||||
del /f /q X:\sm\collector-error.txt 2>nul
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File X:\sm\Collector.ps1
|
||||
if errorlevel 1 (
|
||||
REM Collector failed or was cancelled -> fall back to the default answer file so install still proceeds.
|
||||
set RC=%errorlevel%
|
||||
if %RC% GEQ 1 (
|
||||
echo.
|
||||
echo Collector exited with code %RC% -- falling back to default unattended install.
|
||||
if exist X:\sm\collector-error.txt type X:\sm\collector-error.txt
|
||||
echo (pausing ~25s so this is readable on the console)
|
||||
ping -n 26 127.0.0.1 >nul
|
||||
start /wait "%SETUP%" /unattend:X:\autounattend.xml
|
||||
)
|
||||
exit /b 0
|
||||
|
||||
BIN
windows/drivers/netkvm/netkvm.cat
Normal file
BIN
windows/drivers/netkvm/netkvm.cat
Normal file
Binary file not shown.
375
windows/drivers/netkvm/netkvm.inf
Normal file
375
windows/drivers/netkvm/netkvm.inf
Normal file
@@ -0,0 +1,375 @@
|
||||
;-------------------------------------------------------------------------------
|
||||
;Copyright (c) 2008-2021 Red Hat Inc.
|
||||
;
|
||||
;
|
||||
;Module Name:
|
||||
; netkvm.inf
|
||||
;
|
||||
; VirtIO Ethernet Adapter
|
||||
;
|
||||
;-------------------------------------------------------------------------------
|
||||
;Installation Notes:
|
||||
; Step by step driver installation wiki:
|
||||
; https://github.com/virtio-win/kvm-guest-drivers-windows/wiki/Driver-installation
|
||||
;
|
||||
|
||||
[version]
|
||||
Signature = "$Windows NT$"
|
||||
Class = Net
|
||||
CatalogFile = netkvm.cat
|
||||
ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318}
|
||||
Provider=%VENDOR%
|
||||
DriverVer = 07/09/2025,100.101.104.28500
|
||||
DriverPackageType = PlugAndPlay
|
||||
DriverPackageDisplayName = %kvmnet6.DeviceDesc%
|
||||
PnpLockDown=1
|
||||
|
||||
[Manufacturer]
|
||||
%VENDOR% = NetKVM, NTamd64.10.0...16299
|
||||
|
||||
[NetKVM.NTamd64.10.0...16299]
|
||||
%kvmnet6.DeviceDesc% = kvmnet6.ndi, PCI\VEN_1AF4&DEV_1000&SUBSYS_00011AF4&REV_00, PCI\VEN_1AF4&DEV_1000
|
||||
%kvmnet6.DeviceDesc% = kvmnet6.ndi, PCI\VEN_1AF4&DEV_1041&SUBSYS_11001AF4&REV_01, PCI\VEN_1AF4&DEV_1041
|
||||
|
||||
[kvmnet6.ndi.hw]
|
||||
AddReg = kvmnet6.EnableMSI
|
||||
|
||||
[kvmnet6.EnableMSI]
|
||||
;HKR, "Interrupt Management",, 0x00000010
|
||||
;HKR, "Interrupt Management\MessageSignaledInterruptProperties",, 0x00000010
|
||||
HKR, "Interrupt Management\MessageSignaledInterruptProperties", MSISupported, 0x00010001, 1
|
||||
HKR, "Interrupt Management\MessageSignaledInterruptProperties", MessageNumberLimit, 0x00010001, 2048
|
||||
;HKR, "Interrupt Management\Affinity Policy",, 0x00000010
|
||||
HKR, "Interrupt Management\Affinity Policy", DevicePolicy, 0x00010001, 0
|
||||
HKR, "Interrupt Management\Affinity Policy", DevicePriority, 0x00010001, 2
|
||||
|
||||
[kvmnet6.ndi]
|
||||
Characteristics = 0x84 ; NCF_PHYSICAL | NCF_HAS_UI
|
||||
BusType = 5 ; PCI
|
||||
AddReg = kvmnet6.Reg, Parameters
|
||||
CopyFiles = kvmnet6.CopyFiles, netkvmp.CopyFiles
|
||||
*IfType = 6
|
||||
*MediaType = 0 ; NdisMedium802_3
|
||||
*PhysicalMediaType = 0 ; NdisPhysicalMediumUnspecified
|
||||
|
||||
[kvmnet6.ndi.Services]
|
||||
AddService = netkvm, 2, kvmnet6.Service, kvmnet6.EventLog
|
||||
|
||||
;-----------------------------------------------------------------------------
|
||||
; Red Hat ParaVirtualized Miniport Common
|
||||
;-----------------------------------------------------------------------------
|
||||
[Parameters]
|
||||
|
||||
HKR, Ndi\Params\Priority, ParamDesc, 0, %Priority%
|
||||
HKR, Ndi\Params\Priority, Default, 0, "1"
|
||||
HKR, Ndi\Params\Priority, type, 0, "enum"
|
||||
HKR, Ndi\Params\Priority\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\Priority\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*PriorityVLANTag, ParamDesc, 0, %PriorityVlanTag%
|
||||
HKR, Ndi\Params\*PriorityVLANTag, Default, 0, "3"
|
||||
HKR, Ndi\Params\*PriorityVLANTag, type, 0, "enum"
|
||||
HKR, Ndi\Params\*PriorityVLANTag\enum, "3", 0, %Priority_Vlan%
|
||||
HKR, Ndi\Params\*PriorityVLANTag\enum, "2", 0, %VLan%
|
||||
HKR, Ndi\Params\*PriorityVLANTag\enum, "1", 0, %PriorityOnly%
|
||||
HKR, Ndi\Params\*PriorityVLANTag\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\params\VlanID, ParamDesc, 0, %VLan_ID%
|
||||
HKR, Ndi\params\VlanID, type, 0, "long"
|
||||
HKR, Ndi\params\VlanID, default, 0, "0"
|
||||
HKR, Ndi\params\VlanID, min, 0, "0"
|
||||
HKR, Ndi\params\VlanID, max, 0, "4094"
|
||||
|
||||
HKR, Ndi\Params\DoLog, ParamDesc, 0, %EnableLogging%
|
||||
HKR, Ndi\Params\DoLog, Default, 0, "1"
|
||||
HKR, Ndi\Params\DoLog, type, 0, "enum"
|
||||
HKR, Ndi\Params\DoLog\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\DoLog\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\params\DebugLevel, ParamDesc, 0, %DebugLevel%
|
||||
HKR, Ndi\params\DebugLevel, type, 0, "int"
|
||||
HKR, Ndi\params\DebugLevel, default, 0, "0"
|
||||
HKR, Ndi\params\DebugLevel, min, 0, "0"
|
||||
HKR, Ndi\params\DebugLevel, max, 0, "8"
|
||||
HKR, Ndi\params\DebugLevel, step, 0, "1"
|
||||
|
||||
HKR, Ndi\params\*JumboPacket, ParamDesc, 0, %JumboPacket%
|
||||
HKR, Ndi\params\*JumboPacket, type, 0, "long"
|
||||
HKR, Ndi\params\*JumboPacket, default, 0, "1514"
|
||||
HKR, Ndi\params\*JumboPacket, min, 0, "590"
|
||||
HKR, Ndi\params\*JumboPacket, max, 0, "65500"
|
||||
HKR, Ndi\params\*JumboPacket, step, 0, "1"
|
||||
|
||||
HKR, Ndi\params\TxCapacity, ParamDesc, 0, %TxCapacity%
|
||||
HKR, Ndi\params\TxCapacity, type, 0, "enum"
|
||||
HKR, Ndi\params\TxCapacity, default, 0, "1024"
|
||||
HKR, Ndi\Params\TxCapacity\enum, "16", 0, %String_16%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "32", 0, %String_32%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "64", 0, %String_64%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "128", 0, %String_128%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "256", 0, %String_256%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "512", 0, %String_512%
|
||||
HKR, Ndi\Params\TxCapacity\enum, "1024", 0, %String_1024%
|
||||
|
||||
HKR, Ndi\params\RxCapacity, ParamDesc, 0, %RxCapacity%
|
||||
HKR, Ndi\params\RxCapacity, type, 0, "enum"
|
||||
HKR, Ndi\params\RxCapacity, default, 0, "1024"
|
||||
HKR, Ndi\Params\RxCapacity\enum, "16", 0, %String_16%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "32", 0, %String_32%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "64", 0, %String_64%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "128", 0, %String_128%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "256", 0, %String_256%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "512", 0, %String_512%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "1024", 0, %String_1024%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "2048", 0, %String_2048%
|
||||
HKR, Ndi\Params\RxCapacity\enum, "4096", 0, %String_4096%
|
||||
|
||||
HKR, Ndi\Params\SeparateTail, ParamDesc, 0, %SeparateTail%
|
||||
HKR, Ndi\Params\SeparateTail, Default, 0, "1"
|
||||
HKR, Ndi\Params\SeparateTail, type, 0, "enum"
|
||||
HKR, Ndi\Params\SeparateTail\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\SeparateTail\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\FastInit, ParamDesc, 0, %FastInit%
|
||||
HKR, Ndi\Params\FastInit, Default, 0, "1"
|
||||
HKR, Ndi\Params\FastInit, type, 0, "enum"
|
||||
HKR, Ndi\Params\FastInit\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\FastInit\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\params\NetworkAddress, ParamDesc, 0, %NetworkAddress%
|
||||
HKR, Ndi\params\NetworkAddress, type, 0, "edit"
|
||||
HKR, Ndi\params\NetworkAddress, Optional, 0, "1"
|
||||
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum, ParamDesc, 0, %OffLoad.TxChecksum%
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum, Default, 0, "31"
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum, type, 0, "enum"
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum\enum, "31", 0, %All%
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum\enum, "27", 0, %TCPUDPAll%
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum\enum, "3", 0, %TCPUDPv4%
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum\enum, "1", 0, %TCPv4%
|
||||
HKR, Ndi\Params\OffLoad.TxChecksum\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\OffLoad.TxLSO, ParamDesc, 0, %OffLoad.TxLSO%
|
||||
HKR, Ndi\Params\OffLoad.TxLSO, Default, 0, "2"
|
||||
HKR, Ndi\Params\OffLoad.TxLSO, type, 0, "enum"
|
||||
HKR, Ndi\Params\OffLoad.TxLSO\enum, "2", 0, %Maximal%
|
||||
HKR, Ndi\Params\OffLoad.TxLSO\enum, "1", 0, %IPv4%
|
||||
HKR, Ndi\Params\OffLoad.TxLSO\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\OffLoad.RxCS, ParamDesc, 0, %OffLoad.RxCS%
|
||||
HKR, Ndi\Params\OffLoad.RxCS, Default, 0, "31"
|
||||
HKR, Ndi\Params\OffLoad.RxCS, type, 0, "enum"
|
||||
HKR, Ndi\Params\OffLoad.RxCS\enum, "31", 0, %All%
|
||||
HKR, Ndi\Params\OffLoad.RxCS\enum, "27", 0, %TCPUDPAll%
|
||||
HKR, Ndi\Params\OffLoad.RxCS\enum, "3", 0, %TCPUDPv4%
|
||||
HKR, Ndi\Params\OffLoad.RxCS\enum, "1", 0, %TCPv4%
|
||||
HKR, Ndi\Params\OffLoad.RxCS\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4, ParamDesc, 0, %Std.IPChecksumOffloadv4%
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4, Default, 0, "3"
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4, type, 0, "enum"
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4\enum, "3", 0, %TxRx%
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4\enum, "2", 0, %Rx%
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4\enum, "1", 0, %Tx%
|
||||
HKR, Ndi\Params\*IPChecksumOffloadIPv4\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*LsoV2IPv4, ParamDesc, 0, %Std.LsoV2IPv4%
|
||||
HKR, Ndi\Params\*LsoV2IPv4, Default, 0, "1"
|
||||
HKR, Ndi\Params\*LsoV2IPv4, type, 0, "enum"
|
||||
HKR, Ndi\Params\*LsoV2IPv4\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\*LsoV2IPv4\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*LsoV2IPv6, ParamDesc, 0, %Std.LsoV2IPv6%
|
||||
HKR, Ndi\Params\*LsoV2IPv6, Default, 0, "1"
|
||||
HKR, Ndi\Params\*LsoV2IPv6, type, 0, "enum"
|
||||
HKR, Ndi\Params\*LsoV2IPv6\enum, "1", 0, %Enable%
|
||||
HKR, Ndi\Params\*LsoV2IPv6\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4, ParamDesc, 0, %Std.UDPChecksumOffloadIPv4%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4, Default, 0, "3"
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4, type, 0, "enum"
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4\enum, "3", 0, %TxRx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4\enum, "2", 0, %Rx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4\enum, "1", 0, %Tx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv4\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4, ParamDesc, 0, %Std.TCPChecksumOffloadIPv4%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4, Default, 0, "3"
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4, type, 0, "enum"
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4\enum, "3", 0, %TxRx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4\enum, "2", 0, %Rx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4\enum, "1", 0, %Tx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv4\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6, ParamDesc, 0, %Std.TCPChecksumOffloadIPv6%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6, Default, 0, "3"
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6, type, 0, "enum"
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6\enum, "3", 0, %TxRx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6\enum, "2", 0, %Rx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6\enum, "1", 0, %Tx%
|
||||
HKR, Ndi\Params\*TCPChecksumOffloadIPv6\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6, ParamDesc, 0, %Std.UDPChecksumOffloadIPv6%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6, Default, 0, "3"
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6, type, 0, "enum"
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6\enum, "3", 0, %TxRx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6\enum, "2", 0, %Rx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6\enum, "1", 0, %Tx%
|
||||
HKR, Ndi\Params\*UDPChecksumOffloadIPv6\enum, "0", 0, %Disable%
|
||||
|
||||
HKR, Ndi\params\MinRxBufferPercent, ParamDesc, 0, %MinRxBufferPercent%
|
||||
HKR, Ndi\params\MinRxBufferPercent, type, 0, "int"
|
||||
HKR, Ndi\params\MinRxBufferPercent, default, 0, "0"
|
||||
HKR, Ndi\params\MinRxBufferPercent, min, 0, "0"
|
||||
HKR, Ndi\params\MinRxBufferPercent, max, 0, "100"
|
||||
HKR, Ndi\params\MinRxBufferPercent, step, 0, "1"
|
||||
|
||||
[kvmnet6.CopyFiles]
|
||||
netkvm.sys,,,2
|
||||
|
||||
[netkvmp.CopyFiles]
|
||||
netkvmp.exe,,,2
|
||||
|
||||
[kvmnet6.Service]
|
||||
DisplayName = %kvmnet6.Service.DispName%
|
||||
ServiceType = 1 ;%SERVICE_KERNEL_DRIVER%
|
||||
StartType = 3 ;%SERVICE_DEMAND_START%
|
||||
ErrorControl = 1 ;%SERVICE_ERROR_NORMAL%
|
||||
ServiceBinary = %13%\netkvm.sys
|
||||
LoadOrderGroup = NDIS
|
||||
AddReg = TextModeFlags.Reg
|
||||
|
||||
[kvmnet6.EventLog]
|
||||
AddReg = kvmnet6.AddEventLog.Reg
|
||||
|
||||
[kvmnet6.AddEventLog.Reg]
|
||||
HKR, , EventMessageFile, 0x00020000, "%%SystemRoot%%\System32\netevent.dll"
|
||||
HKR, , TypesSupported, 0x00010001, 7
|
||||
|
||||
[TextModeFlags.Reg]
|
||||
HKR,,TextModeFlags,0x00010001, 0x0001
|
||||
HKR,Parameters,DisableMSI,,"0"
|
||||
HKR,Parameters,EarlyDebug,,"3"
|
||||
HKR,Parameters,DmaRemappingCompatible,0x00010001,2
|
||||
|
||||
|
||||
[SourceDisksNames]
|
||||
1 = %DiskId1%,,,""
|
||||
|
||||
[SourceDisksFiles]
|
||||
netkvm.sys = 1,,
|
||||
netkvmp.exe = 1,,
|
||||
|
||||
[DestinationDirs]
|
||||
kvmnet6.CopyFiles = 13
|
||||
netkvmp.CopyFiles = 11
|
||||
|
||||
[Strings]
|
||||
VENDOR = "Red Hat, Inc."
|
||||
kvmnet6.DeviceDesc = "Red Hat VirtIO Ethernet Adapter"
|
||||
kvmnet6.Service.DispName = "Red Hat VirtIO Ethernet Adapter Service"
|
||||
DiskId1 = "Red Hat VirtIO Ethernet Adapter Driver Disk #1"
|
||||
NetworkAddress = "Assign MAC"
|
||||
Priority = "Init.Do802.1PQ"
|
||||
JumboPacket = "Jumbo Packet"
|
||||
TxCapacity = "Init.MaxTxBuffers"
|
||||
RxCapacity = "Init.MaxRxBuffers"
|
||||
SeparateTail = "Init.SeparateRxTail"
|
||||
FastInit = "Fast Initialization"
|
||||
Offload.TxChecksum = "Offload.Tx.Checksum"
|
||||
Offload.TxLSO = "Offload.Tx.LSO"
|
||||
Offload.RxCS = "Offload.Rx.Checksum"
|
||||
EnableLogging = "Logging.Enable"
|
||||
DebugLevel = "Logging.Level"
|
||||
Tx = "Tx Enabled";
|
||||
Rx = "Rx Enabled";
|
||||
TxRx = "Rx & Tx Enabled";
|
||||
Std.LsoV2IPv4 = "Large Send Offload V2 (IPv4)"
|
||||
Std.LsoV2IPv6 = "Large Send Offload V2 (IPv6)"
|
||||
Std.UDPChecksumOffloadIPv4 = "UDP Checksum Offload (IPv4)"
|
||||
Std.TCPChecksumOffloadIPv4 = "TCP Checksum Offload (IPv4)"
|
||||
Std.UDPChecksumOffloadIPv6 = "UDP Checksum Offload (IPv6)"
|
||||
Std.TCPChecksumOffloadIPv6 = "TCP Checksum Offload (IPv6)"
|
||||
Std.IPChecksumOffloadv4 = "IPv4 Checksum Offload"
|
||||
Disable = "Disabled"
|
||||
Enable = "Enabled"
|
||||
Enable* = "Enabled*"
|
||||
String_16 = "16"
|
||||
String_32 = "32"
|
||||
String_64 = "64"
|
||||
String_128 = "128"
|
||||
String_256 = "256"
|
||||
String_512 = "512"
|
||||
String_1024 = "1024"
|
||||
String_2048 = "2048"
|
||||
String_4096 = "4096"
|
||||
PriorityVlanTag = "Priority and VLAN tagging"
|
||||
PriorityOnly = "Priority"
|
||||
VLan = "VLan"
|
||||
VLan_ID = "VLan ID"
|
||||
Priority_Vlan = "All"
|
||||
10M = "10M"
|
||||
100M = "100M"
|
||||
1G = "1G"
|
||||
10G = "10G"
|
||||
TCPv4 = "TCP(v4)"
|
||||
TCPUDPv4 = "TCP/UDP(v4)"
|
||||
TCPUDPAll = "TCP/UDP(v4,v6)"
|
||||
All = "All"
|
||||
IPv4 = "IPv4"
|
||||
Maximal = "Maximal"
|
||||
MinRxBufferPercent = "MinRxBufferPercent"
|
||||
|
||||
[kvmnet6.Reg]
|
||||
HKR, , BusNumber, 0, "0"
|
||||
HKR, Ndi, Service, 0, "netkvm"
|
||||
HKR, Ndi\Interfaces, UpperRange, 0, "ndis5"
|
||||
HKR, Ndi\Interfaces, LowerRange, 0, "ethernet"
|
||||
|
||||
HKR, Ndi\params\*RSS, ParamDesc, 0, "Receive Side Scaling"
|
||||
HKR, Ndi\params\*RSS, Type, 0, "enum"
|
||||
HKR, Ndi\params\*RSS, Default, 0, "1"
|
||||
HKR, Ndi\params\*RSS, Optional, 0, "0"
|
||||
HKR, Ndi\params\*RSS\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*RSS\enum, "1", 0, "Enabled"
|
||||
|
||||
HKR, Ndi\params\*NumRssQueues, ParamDesc, 0, "Maximum Number of RSS Queues"
|
||||
HKR, Ndi\params\*NumRssQueues, type, 0, "int"
|
||||
HKR, Ndi\params\*NumRssQueues, default, 0, "16"
|
||||
HKR, Ndi\params\*NumRssQueues, min, 0, "1"
|
||||
HKR, Ndi\params\*NumRssQueues, max, 0, "32"
|
||||
HKR, Ndi\params\*NumRssQueues, step, 0, "1"
|
||||
|
||||
HKR, Ndi\params\*RscIPv4, ParamDesc, 0, "Recv Segment Coalescing (IPv4)"
|
||||
HKR, Ndi\params\*RscIPv4, Type, 0, "enum"
|
||||
HKR, Ndi\params\*RscIPv4, Default, 0, "1"
|
||||
HKR, Ndi\params\*RscIPv4, Optional, 0, "0"
|
||||
HKR, Ndi\params\*RscIPv4\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*RscIPv4\enum, "1", 0, "Enabled"
|
||||
|
||||
HKR, Ndi\params\*RscIPv6, ParamDesc, 0, "Recv Segment Coalescing (IPv6)"
|
||||
HKR, Ndi\params\*RscIPv6, Type, 0, "enum"
|
||||
HKR, Ndi\params\*RscIPv6, Default, 0, "1"
|
||||
HKR, Ndi\params\*RscIPv6, Optional, 0, "0"
|
||||
HKR, Ndi\params\*RscIPv6\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*RscIPv6\enum, "1", 0, "Enabled"
|
||||
HKR, Ndi\params\*UsoIPv4, ParamDesc, 0, "UDP Segmentation Offload (IPv4)"
|
||||
HKR, Ndi\params\*UsoIPv4, Type, 0, "enum"
|
||||
HKR, Ndi\params\*UsoIPv4, Default, 0, "1"
|
||||
HKR, Ndi\params\*UsoIPv4, Optional, 0, "0"
|
||||
HKR, Ndi\params\*UsoIPv4\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*UsoIPv4\enum, "1", 0, "Enabled"
|
||||
|
||||
HKR, Ndi\params\*UsoIPv6, ParamDesc, 0, "UDP Segmentation Offload (IPv6)"
|
||||
HKR, Ndi\params\*UsoIPv6, Type, 0, "enum"
|
||||
HKR, Ndi\params\*UsoIPv6, Default, 0, "1"
|
||||
HKR, Ndi\params\*UsoIPv6, Optional, 0, "0"
|
||||
HKR, Ndi\params\*UsoIPv6\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*UsoIPv6\enum, "1", 0, "Enabled"
|
||||
|
||||
HKR, Ndi\params\*NdisPoll, ParamDesc, 0, "Ndis Poll Mode"
|
||||
HKR, Ndi\params\*NdisPoll, Type, 0, "enum"
|
||||
HKR, Ndi\params\*NdisPoll, Default, 0, "1"
|
||||
HKR, Ndi\params\*NdisPoll, Optional, 0, "0"
|
||||
HKR, Ndi\params\*NdisPoll\enum, "0", 0, "Disabled"
|
||||
HKR, Ndi\params\*NdisPoll\enum, "1", 0, "Enabled"
|
||||
BIN
windows/drivers/netkvm/netkvm.sys
Normal file
BIN
windows/drivers/netkvm/netkvm.sys
Normal file
Binary file not shown.
BIN
windows/drivers/netkvm/netkvmco.exe
Normal file
BIN
windows/drivers/netkvm/netkvmco.exe
Normal file
Binary file not shown.
BIN
windows/drivers/netkvm/netkvmp.exe
Normal file
BIN
windows/drivers/netkvm/netkvmp.exe
Normal file
Binary file not shown.
@@ -118,11 +118,19 @@
|
||||
render when launched as a bare Shell Launcher shell). Configure-Kiosk.ps1
|
||||
bakes the silent-elevation UAC policy + the lockdown (Keyboard Filter,
|
||||
DisableTaskMgr, hidden taskbar); the wizard runs fullscreen-topmost on top.
|
||||
|
||||
Launch is via a single hidden-window PowerShell (no `cmd /c` wrapper): the
|
||||
old `cmd /c powershell ...` spawned an extra process AND flashed a visible
|
||||
console window on the bare first-boot desktop — which itself read as "the
|
||||
machine is doing something broken" before the wizard appeared. `-WindowStyle
|
||||
Hidden` + dropping the cmd shim removes that flash and one process off the
|
||||
critical path. Elevation (-Verb RunAs) is still required for ApplyService
|
||||
(account/BitLocker/hardening) and is silent thanks to the baked UAC policy.
|
||||
-->
|
||||
<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>
|
||||
<CommandLine>powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command "Start-Process -FilePath 'C:\Program Files\SilverOS\Welcome\SilverOS.Welcome.App.exe' -Verb RunAs"</CommandLine>
|
||||
<Description>Launch SilverOS Welcome elevated</Description>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
||||
|
||||
@@ -130,8 +130,13 @@ function Invoke-ForceLegacySetup {
|
||||
Copy-Item (Join-Path $PSScriptRoot '..\collector\*') $smDir -Recurse -Force
|
||||
Copy-Item (Join-Path $smDir 'winpeshl.ini') (Join-Path $bootmnt 'Windows\System32\winpeshl.ini') -Force
|
||||
Write-Host " staged collector to boot.wim \sm\ + winpeshl.ini"
|
||||
$setup = if (Test-Path (Join-Path $bootmnt 'sources\setup.exe')) { 'X:\sources\setup.exe' } else { 'X:\setup.exe' }
|
||||
$cmdline = "$setup /unattend:X:\autounattend.xml"
|
||||
# Setup\CmdLine is the WinPE setup-image shell launch and is AUTHORITATIVE over
|
||||
# winpeshl.ini -- point it at the SilverMetal collector so the pre-config UI runs
|
||||
# FIRST. The collector then launches the LEGACY setup.exe itself (X:\sources\setup.exe,
|
||||
# preserving the legacy-Setup bypass) with its generated answer file, or falls back to
|
||||
# the default autounattend.xml on cancel/error. (Pointing Setup\CmdLine straight at
|
||||
# setup.exe bypassed the collector entirely -- it won over winpeshl.ini.)
|
||||
$cmdline = "cmd /c X:\sm\Start-Collector.cmd"
|
||||
$hive = Join-Path $bootmnt 'Windows\System32\config\SYSTEM'
|
||||
& reg load 'HKLM\SM_BOOT' $hive | Out-Null
|
||||
try {
|
||||
@@ -155,6 +160,12 @@ function Invoke-PublishWelcome {
|
||||
Write-Stage 'Stage 3b: publish SilverOS Welcome app (win-x64 self-contained)'
|
||||
$proj = Join-Path $WindowsDir 'welcome\src\SilverOS.Welcome.App'
|
||||
$out = Join-Path $WorkDir 'welcome-publish'
|
||||
# Force a CLEAN compile. The CI runner reuses build artifacts across runs, and dotnet's
|
||||
# incremental build has shipped a STALE SilverOS.Welcome.Core.dll (old code despite fixed
|
||||
# source) -- so wipe every bin/obj under welcome/ before publishing (a clean tree forces a
|
||||
# full recompile; note `dotnet publish` does NOT accept --no-incremental).
|
||||
Get-ChildItem (Join-Path $WindowsDir 'welcome') -Recurse -Directory -EA SilentlyContinue |
|
||||
Where-Object { $_.Name -in 'bin', 'obj' } | Remove-Item -Recurse -Force -EA SilentlyContinue
|
||||
& dotnet publish $proj -c Release -f net9.0-windows10.0.19041.0 -r win-x64 --self-contained true -o $out
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Welcome app dotnet publish failed' }
|
||||
Write-Host " Published to: $out"
|
||||
@@ -195,6 +206,22 @@ function Copy-WelcomePayload {
|
||||
} else {
|
||||
Write-Warning " No apps dir found at $appsDir -- image will ship with no app catalog."
|
||||
}
|
||||
# Stage the fixed-version WebView2 runtime, if vendored, next to the app.
|
||||
# Cold-start + air-gap: a fixed-version runtime is just files (no installer
|
||||
# step at first boot) and removes the dependency on whether IoT Enterprise LTSC
|
||||
# ships WebView2 at all. Operator populates windows\welcome\runtime\webview2\
|
||||
# with an EXTRACTED "Microsoft Edge WebView2 Fixed Version" distribution (the
|
||||
# folder that contains msedgewebview2.exe) -- handled like the drivers dir:
|
||||
# absent is allowed (VM/dev test), in which case the app falls back to Evergreen.
|
||||
$wv2Src = Join-Path $WindowsDir 'welcome\runtime\webview2'
|
||||
if (Test-Path (Join-Path $wv2Src 'msedgewebview2.exe')) {
|
||||
$wv2Dest = Join-Path $dest 'webview2'
|
||||
$null = New-Item -ItemType Directory -Force $wv2Dest
|
||||
Copy-Item (Join-Path $wv2Src '*') $wv2Dest -Recurse -Force
|
||||
Write-Host " Staged fixed-version WebView2 runtime to $wv2Dest"
|
||||
} else {
|
||||
Write-Warning " No fixed-version WebView2 runtime at $wv2Src (expected msedgewebview2.exe) -- image will rely on the Evergreen runtime being present at first boot. See windows\welcome\runtime\webview2\README.md."
|
||||
}
|
||||
# --- Guard: verify the payload actually landed in the mounted image -------
|
||||
$stagedExe = Join-Path $dest 'SilverOS.Welcome.App.exe'
|
||||
if (-not (Test-Path $stagedExe)) {
|
||||
@@ -233,7 +260,13 @@ function Invoke-ServiceWim {
|
||||
# Drivers (GPD Pocket 4 pack) -- skipped silently if dir empty (e.g. VM test).
|
||||
$drv = Join-Path $WindowsDir 'drivers'
|
||||
if ((Get-ChildItem $drv -Recurse -Filter *.inf -EA SilentlyContinue)) {
|
||||
Write-Host ' adding drivers'; Add-WindowsDriver -Path $mount -Driver $drv -Recurse | Out-Null
|
||||
# -ForceUnsigned: skip the offline-inject signature check (the virtio NetKVM
|
||||
# driver is WHQL-signed and loads fine at boot; the offline check can still
|
||||
# reject it on the build host). Non-fatal: a driver issue must not brick the
|
||||
# whole image build -- warn and continue without it.
|
||||
Write-Host ' adding drivers'
|
||||
try { Add-WindowsDriver -Path $mount -Driver $drv -Recurse -ForceUnsigned -ErrorAction Stop | Out-Null }
|
||||
catch { Write-Warning " driver inject failed (continuing without it): $($_.Exception.Message)" }
|
||||
} else { Write-Host ' no .inf drivers staged (ok for VM test)' }
|
||||
|
||||
# Kiosk features (Shell Launcher v2 + Keyboard Filter) — IoT Enterprise LTSC.
|
||||
|
||||
@@ -54,16 +54,19 @@ Describe 'New-SmAnswerFile' {
|
||||
}
|
||||
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
|
||||
It 'preconfig round-trips via chunked FirstLogonCommands' {
|
||||
# Gather the echo'd base64 chunks in Order, concatenate, strip whitespace, decode.
|
||||
$chunks = [regex]::Matches($script:xml, 'echo ([A-Za-z0-9+/=]+)') | ForEach-Object { $_.Groups[1].Value }
|
||||
$b64 = ($chunks -join '')
|
||||
$json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b64)) | ConvertFrom-Json
|
||||
$json.flavour | Should -Be 'developer'
|
||||
$json.bitlocker.pin | Should -Be '246810'
|
||||
}
|
||||
It 'has no specialize pass anymore' { $script:xml | Should -Not -Match 'pass="specialize"' }
|
||||
It 'creates the preconfig dir + decodes it at first logon' {
|
||||
$script:xml | Should -Match 'ProgramData\\SilverMetal'
|
||||
$script:xml | Should -Match 'FromBase64String'
|
||||
$script:xml | Should -Match 'preconfig\.json'
|
||||
}
|
||||
It 'launches the toolbox in FirstLogonCommands' { $script:xml | Should -Match 'SilverOS\.Welcome\.App\.exe' }
|
||||
}
|
||||
|
||||
44
windows/welcome/runtime/webview2/README.md
Normal file
44
windows/welcome/runtime/webview2/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Fixed-version WebView2 runtime (vendored)
|
||||
|
||||
The SilverOS Welcome wizard is a MAUI Blazor Hybrid app — it needs the **Microsoft
|
||||
Edge WebView2 Runtime**. IoT Enterprise LTSC images frequently ship **without** it,
|
||||
and even when present, the Evergreen runtime adds first-boot cold-start cost (registry
|
||||
probe; on-demand install if absent). To make first boot fast *and* air-gapped, we bake
|
||||
a **fixed-version** runtime here and point the app at it via
|
||||
`WEBVIEW2_BROWSER_EXECUTABLE_FOLDER` (see `MauiProgram.cs`).
|
||||
|
||||
## What goes in this folder
|
||||
|
||||
The **extracted** contents of a "Microsoft Edge WebView2 Fixed Version" distribution —
|
||||
i.e. this directory must directly contain `msedgewebview2.exe` (plus its sibling DLLs,
|
||||
`*.pak`, locales, etc.). The build (`windows/installer/build.ps1`,
|
||||
`Copy-WelcomePayload`) detects `msedgewebview2.exe` and copies the whole folder to
|
||||
`C:\Program Files\SilverOS\Welcome\webview2\` inside the image.
|
||||
|
||||
```
|
||||
windows/welcome/runtime/webview2/
|
||||
├── README.md <- this file (the only thing committed)
|
||||
├── msedgewebview2.exe <- you add these
|
||||
├── *.dll
|
||||
├── *.pak
|
||||
└── ...
|
||||
```
|
||||
|
||||
## How to obtain it
|
||||
|
||||
1. Download the **Fixed Version** (x64) CAB from the official WebView2 distribution
|
||||
page: <https://developer.microsoft.com/microsoft-edge/webview2/> → "Fixed Version".
|
||||
Match the channel/arch to the target (x64, since the app publishes `win-x64`).
|
||||
2. Expand the CAB and copy the inner runtime folder's contents here so that
|
||||
`msedgewebview2.exe` sits directly in this directory.
|
||||
3. Pin the version in `windows/installer/inputs.manifest.json` alongside the other
|
||||
baked inputs (SBOM hygiene).
|
||||
|
||||
## If you skip this
|
||||
|
||||
The build does **not** fail — it logs a warning and the image relies on whatever
|
||||
Evergreen runtime is present at first boot. Fine for a quick VM smoke test; **not**
|
||||
recommended for shipped LTSC media (risk of a blank/hung wizard and slower cold start).
|
||||
|
||||
> The runtime binaries are **not** committed (large, Microsoft-redistributable, version-
|
||||
> pinned per build). Only this README is tracked.
|
||||
@@ -5,12 +5,54 @@
|
||||
xmlns:components="clr-namespace:SilverOS.Welcome.App.Components;assembly=SilverOS.Welcome.UI"
|
||||
x:Class="SilverOS.Welcome.App.MainPage">
|
||||
|
||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html"
|
||||
BlazorWebViewInitialized="OnBlazorInitialized"
|
||||
UrlLoading="OnUrlLoading">
|
||||
<BlazorWebView.RootComponents>
|
||||
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
||||
</BlazorWebView.RootComponents>
|
||||
</BlazorWebView>
|
||||
<!--
|
||||
A native MAUI splash sits ON TOP of the BlazorWebView. MAUI controls render
|
||||
immediately when the window is shown — they do NOT wait on WebView2/.NET JIT —
|
||||
so the user sees branded "loading" within the first frame instead of a blank
|
||||
window for the seconds it takes WebView2 to cold-start and Blazor to boot.
|
||||
The overlay is dismissed in MainPage.xaml.cs once WV2 finishes its first
|
||||
navigation (the index.html splash then carries the eye through Blazor's boot).
|
||||
-->
|
||||
<Grid BackgroundColor="#0b0f14">
|
||||
|
||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html"
|
||||
BlazorWebViewInitialized="OnBlazorInitialized"
|
||||
UrlLoading="OnUrlLoading">
|
||||
<BlazorWebView.RootComponents>
|
||||
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
||||
</BlazorWebView.RootComponents>
|
||||
</BlazorWebView>
|
||||
|
||||
<Grid x:Name="SplashOverlay"
|
||||
BackgroundColor="#0b0f14"
|
||||
InputTransparent="False">
|
||||
<VerticalStackLayout HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
Spacing="22">
|
||||
<Label Text="SilverOS"
|
||||
HorizontalOptions="Center"
|
||||
FontFamily="OpenSansRegular"
|
||||
FontSize="42"
|
||||
FontAutoScalingEnabled="False"
|
||||
TextColor="#e8edf5" />
|
||||
<Label Text="WELCOME"
|
||||
HorizontalOptions="Center"
|
||||
FontSize="13"
|
||||
CharacterSpacing="8"
|
||||
TextColor="#00d4ff" />
|
||||
<ActivityIndicator IsRunning="True"
|
||||
Color="#00d4ff"
|
||||
HeightRequest="34"
|
||||
WidthRequest="34"
|
||||
HorizontalOptions="Center"
|
||||
Margin="0,10,0,0" />
|
||||
<Label Text="Preparing your setup…"
|
||||
HorizontalOptions="Center"
|
||||
FontSize="14"
|
||||
TextColor="#8fa4bc" />
|
||||
</VerticalStackLayout>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace SilverOS.Welcome.App;
|
||||
|
||||
public partial class MainPage : ContentPage
|
||||
{
|
||||
bool _splashDismissed;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -20,7 +22,13 @@ public partial class MainPage : ContentPage
|
||||
{
|
||||
var wv = e.WebView; // Microsoft.UI.Xaml.Controls.WebView2
|
||||
wv.NavigationCompleted += (a, b) =>
|
||||
{
|
||||
Diag.Log($"WV2 NavigationCompleted ok={b.IsSuccess} status={b.WebErrorStatus}");
|
||||
// First completed navigation = the WebView has content on screen.
|
||||
// Drop the native splash so the (visually identical) in-page splash
|
||||
// carries through Blazor's final boot without a flash of blank.
|
||||
if (b.IsSuccess) DismissSplash();
|
||||
};
|
||||
if (wv.CoreWebView2 is not null)
|
||||
wv.CoreWebView2.ProcessFailed += (a, b) =>
|
||||
Diag.Log("WV2 ProcessFailed: " + b.ProcessFailedKind);
|
||||
@@ -29,6 +37,26 @@ public partial class MainPage : ContentPage
|
||||
#endif
|
||||
}
|
||||
|
||||
// Fade the native splash out once, then collapse it so it never intercepts input.
|
||||
void DismissSplash()
|
||||
{
|
||||
if (_splashDismissed) return;
|
||||
_splashDismissed = true;
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await SplashOverlay.FadeTo(0, 250, Easing.CubicOut);
|
||||
}
|
||||
catch { /* fade is cosmetic — never block on it */ }
|
||||
finally
|
||||
{
|
||||
SplashOverlay.IsVisible = false;
|
||||
SplashOverlay.InputTransparent = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OnUrlLoading(object? sender, UrlLoadingEventArgs e)
|
||||
=> Diag.Log("UrlLoading: " + e.Url);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,23 @@ public static class MauiProgram
|
||||
Directory.CreateDirectory(wv2);
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_USER_DATA_FOLDER", wv2);
|
||||
|
||||
// Cold-start + air-gap: prefer a fixed-version WebView2 runtime baked next to
|
||||
// the app (build.ps1 stages it under .\webview2). This removes two first-boot
|
||||
// costs at once: (1) the Evergreen-runtime registry probe, and (2) the risk
|
||||
// that IoT Enterprise LTSC ships WITHOUT WebView2 entirely — in which case the
|
||||
// wizard would block/blank waiting on an on-demand install. If the baked folder
|
||||
// is absent (VM/dev test), fall through to whatever Evergreen runtime exists.
|
||||
var fixedRuntime = Path.Combine(AppContext.BaseDirectory, "webview2");
|
||||
if (File.Exists(Path.Combine(fixedRuntime, "msedgewebview2.exe")))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", fixedRuntime);
|
||||
Diag.Log("WebView2: using baked fixed-version runtime at " + fixedRuntime);
|
||||
}
|
||||
else
|
||||
{
|
||||
Diag.Log("WebView2: no baked runtime found; relying on installed Evergreen");
|
||||
}
|
||||
|
||||
var builder = MauiApp.CreateBuilder();
|
||||
builder
|
||||
.UseMauiApp<App>()
|
||||
|
||||
@@ -25,6 +25,16 @@
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
|
||||
<!--
|
||||
Cold-start: precompile to ReadyToRun so the self-contained image does not
|
||||
JIT the whole app on first launch (this wizard runs exactly once, on the
|
||||
slowest possible "fresh OS, cold disk" path, so first-run JIT is pure cost).
|
||||
R2R only — trimming/NativeAOT are NOT safe for MAUI Blazor Hybrid (heavy
|
||||
reflection in Blazor + DI). Larger binaries are fine: the payload is baked
|
||||
into the image, never downloaded. Only takes effect on `dotnet publish -r win-x64`.
|
||||
-->
|
||||
<PublishReadyToRun Condition="'$(RuntimeIdentifier)' != ''">true</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -303,6 +303,58 @@ h1:focus { outline: none; }
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* ── Boot splash (pre-Blazor; mirrors the native MAUI splash) ───────── */
|
||||
.sm-boot {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.4rem;
|
||||
background: var(--clr-void);
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.sm-boot-title {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 2.6rem;
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--clr-text-hi);
|
||||
}
|
||||
|
||||
.sm-boot-kicker {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.55em;
|
||||
text-indent: 0.55em; /* balance the trailing letter-spacing */
|
||||
color: var(--clr-accent);
|
||||
}
|
||||
|
||||
.sm-boot-spinner {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border: 3px solid var(--clr-border-hi);
|
||||
border-top-color: var(--clr-accent);
|
||||
border-radius: 50%;
|
||||
animation: sm-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes sm-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.sm-boot-text {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 0.9rem;
|
||||
color: var(--clr-text-mid);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sm-boot-spinner { animation: none; }
|
||||
}
|
||||
|
||||
/* ── Loading / error states ─────────────────────────────────────────── */
|
||||
.loading {
|
||||
font-family: var(--font-mono);
|
||||
|
||||
@@ -15,7 +15,21 @@
|
||||
|
||||
<div class="status-bar-safe-area"></div>
|
||||
|
||||
<div id="app">Loading...</div>
|
||||
<!--
|
||||
In-page boot splash. Lives INSIDE #app so Blazor wipes it automatically the
|
||||
moment the root component first renders. Styled to match the native MAUI
|
||||
splash (same void bg + electric-ice accent), so handoff native -> webview ->
|
||||
Blazor reads as one continuous loading screen rather than three flashes.
|
||||
Inline-styled on the wrapper so it shows even before app.css paints.
|
||||
-->
|
||||
<div id="app" style="background:#0b0f14">
|
||||
<div class="sm-boot">
|
||||
<div class="sm-boot-title">SilverOS</div>
|
||||
<div class="sm-boot-kicker">WELCOME</div>
|
||||
<div class="sm-boot-spinner" aria-hidden="true"></div>
|
||||
<div class="sm-boot-text">Preparing your setup…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
|
||||
@@ -4,17 +4,38 @@ namespace SilverOS.Welcome.Core.Apps;
|
||||
|
||||
public sealed class AppInstaller(IProcessRunner runner, string appsDir) : IAppInstaller
|
||||
{
|
||||
// Best-effort diagnostic log (winget resolution, bootstrap output, per-app results).
|
||||
// Lives under ProgramData so it survives + is readable post-install. Never throws.
|
||||
private static readonly string LogPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "SilverMetal", "appinstall.log");
|
||||
|
||||
private static void Log(string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(LogPath)!);
|
||||
File.AppendAllText(LogPath, $"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}");
|
||||
}
|
||||
catch { /* logging is best-effort */ }
|
||||
}
|
||||
|
||||
private static string Snip(string? s) =>
|
||||
string.IsNullOrWhiteSpace(s) ? "" : s.Trim().Replace("\r", " ").Replace("\n", " ") is var t && t.Length > 300 ? t[..300] : t;
|
||||
|
||||
public async Task<IReadOnlyList<AppInstallResult>> InstallAsync(
|
||||
IReadOnlyList<AppCatalogEntry> apps, IProgress<ApplyProgress> progress, CancellationToken ct = default)
|
||||
{
|
||||
var results = new List<AppInstallResult>();
|
||||
if (apps.Count == 0) return results;
|
||||
|
||||
Log($"InstallAsync: {apps.Count} app(s) requested: {string.Join(", ", apps.Select(a => a.Id))}");
|
||||
|
||||
// App installs are non-critical: a missing/broken winget (e.g. offline IoT LTSC) must
|
||||
// NEVER fail onboarding. Resolve winget defensively; if it can't be found, skip installs.
|
||||
var winget = await ResolveWingetAsync(progress, ct);
|
||||
if (winget is null)
|
||||
{
|
||||
Log($"winget UNAVAILABLE -> skipping all {apps.Count} app(s)");
|
||||
progress.Report(new($"App installer unavailable - skipping {apps.Count} app(s)", 80));
|
||||
foreach (var app in apps) results.Add(new AppInstallResult(app.Id, false));
|
||||
return results;
|
||||
@@ -33,6 +54,7 @@ public sealed class AppInstaller(IProcessRunner runner, string appsDir) : IAppIn
|
||||
$"install --id {id} --silent --accept-package-agreements --accept-source-agreements --disable-interactivity",
|
||||
ct);
|
||||
ok = r.ExitCode == 0;
|
||||
Log($"install {id}: exit={r.ExitCode} ok={ok} err={Snip(r.StdErr)}");
|
||||
if (ok && !string.IsNullOrWhiteSpace(app.Configure))
|
||||
{
|
||||
var script = Path.Combine(appsDir, "configure", app.Configure);
|
||||
@@ -43,6 +65,7 @@ public sealed class AppInstaller(IProcessRunner runner, string appsDir) : IAppIn
|
||||
}
|
||||
results.Add(new AppInstallResult(app.Id, ok));
|
||||
}
|
||||
Log($"InstallAsync done: {results.Count(r => r.Installed)}/{results.Count} installed");
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -52,22 +75,34 @@ public sealed class AppInstaller(IProcessRunner runner, string appsDir) : IAppIn
|
||||
private async Task<string?> ResolveWingetAsync(IProgress<ApplyProgress> progress, CancellationToken ct)
|
||||
{
|
||||
// 1) Already launchable by name (on PATH for this process)?
|
||||
if ((await TryRunAsync("winget", "--version", ct)).ExitCode == 0) return "winget";
|
||||
var p1 = await TryRunAsync("winget", "--version", ct);
|
||||
Log($"winget probe (PATH): exit={p1.ExitCode} out={Snip(p1.StdOut)}");
|
||||
if (p1.ExitCode == 0) return "winget";
|
||||
|
||||
// 2) Provision App Installer via the bundled bootstrap (or registered package), then re-probe.
|
||||
// 2) Provision App Installer, then re-probe. Run the bootstrap SCRIPT FILE directly
|
||||
// (it checks for winget and installs it online if absent). Invoking the .ps1 file
|
||||
// avoids an inline -Command (a prior inline if/else had an unbalanced-brace parse bug
|
||||
// from a non-interpolated string, so the bootstrap never actually ran).
|
||||
progress.Report(new("Preparing app installer", 68));
|
||||
var bootstrap = Path.Combine(appsDir, "bootstrap-winget.ps1");
|
||||
await TryRunAsync("powershell.exe",
|
||||
$"-NoProfile -ExecutionPolicy Bypass -Command \"if (Test-Path '{bootstrap}') {{ & '{bootstrap}' }} else {{ " +
|
||||
"Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -EA SilentlyContinue }}\"",
|
||||
ct);
|
||||
if ((await TryRunAsync("winget", "--version", ct)).ExitCode == 0) return "winget";
|
||||
var b = File.Exists(bootstrap)
|
||||
? await TryRunAsync("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -File \"{bootstrap}\"", ct)
|
||||
: await TryRunAsync("powershell.exe",
|
||||
"-NoProfile -ExecutionPolicy Bypass -Command \"Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -EA SilentlyContinue\"",
|
||||
ct);
|
||||
Log($"bootstrap-winget: exit={b.ExitCode} out={Snip(b.StdOut)} err={Snip(b.StdErr)}");
|
||||
var p2 = await TryRunAsync("winget", "--version", ct);
|
||||
Log($"winget probe (post-bootstrap): exit={p2.ExitCode} out={Snip(p2.StdOut)}");
|
||||
if (p2.ExitCode == 0) return "winget";
|
||||
|
||||
// 3) Fall back to the WindowsApps execution-alias path (bare-name launch can fail under
|
||||
// UseShellExecute=false even when winget is installed).
|
||||
var local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var aliased = Path.Combine(local, "Microsoft", "WindowsApps", "winget.exe");
|
||||
if (File.Exists(aliased) && (await TryRunAsync(aliased, "--version", ct)).ExitCode == 0) return aliased;
|
||||
var aliasExists = File.Exists(aliased);
|
||||
var p3Exit = aliasExists ? (await TryRunAsync(aliased, "--version", ct)).ExitCode : -1;
|
||||
Log($"winget alias path '{aliased}': exists={aliasExists} probe={p3Exit}");
|
||||
if (aliasExists && p3Exit == 0) return aliased;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@inject IAppCatalog AppCatalog
|
||||
@inject IPreconfigStore PreconfigStore
|
||||
@inject WizardState State
|
||||
@inject SilverOS.Welcome.Core.Apply.IProcessRunner ProcessRunner
|
||||
|
||||
@if (_toolboxHome)
|
||||
{
|
||||
@@ -83,6 +84,10 @@ else
|
||||
@(_currentStep == _stepTitles.Length - 2 ? "Apply" : "Next")
|
||||
</button>
|
||||
}
|
||||
else if (_currentStep == _stepTitles.Length - 1)
|
||||
{
|
||||
<button class="btn-primary" @onclick="RestartNow">Restart now</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -108,6 +113,11 @@ else
|
||||
private string? _error;
|
||||
private IReadOnlyList<FlavourManifest> _flavours = Array.Empty<FlavourManifest>();
|
||||
|
||||
private async Task RestartNow()
|
||||
{
|
||||
await ProcessRunner.RunAsync("cmd.exe", "/c shutdown /r /t 5", default);
|
||||
}
|
||||
|
||||
private bool CanGoNext => _currentStep switch
|
||||
{
|
||||
1 => State.Flavour is not null,
|
||||
@@ -169,11 +179,11 @@ else
|
||||
if (pre.Bitlocker.Enable && !string.IsNullOrEmpty(pre.Bitlocker.Pin))
|
||||
State.BitLockerPin = pre.Bitlocker.Pin;
|
||||
|
||||
// First run with a preconfig: skip the manual walkthrough. Jump straight to the
|
||||
// Apply step and signal it to auto-start (spec §4d: auto-runs once, shows progress
|
||||
// + recovery key, then Done).
|
||||
_currentStep = 4;
|
||||
_autoApply = true;
|
||||
// First run with a preconfig: the collector already captured account + flavour, so
|
||||
// skip Welcome/Flavour and land on the Apps step (pre-checked with the flavour's
|
||||
// defaults) so the user can review/adjust the app selection before applying. From
|
||||
// there it's Apps -> Prefs -> Apply -> Done. (No auto-apply: the picker is the point.)
|
||||
_currentStep = 2;
|
||||
}
|
||||
|
||||
private void ReRunSetup()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using QRCoder
|
||||
@inject SilverOS.Welcome.Core.Apply.IProcessRunner ProcessRunner
|
||||
|
||||
<div class="step done-step">
|
||||
<h1>All Done!</h1>
|
||||
@@ -27,8 +26,6 @@
|
||||
</small></p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn-primary btn-restart" @onclick="RestartNow">Restart Now</button>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@@ -56,9 +53,4 @@
|
||||
catch { /* QR is best-effort; the key text still shows */ }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestartNow()
|
||||
{
|
||||
await ProcessRunner.RunAsync("cmd.exe", "/c shutdown /r /t 5", CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user