Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
b3dbbbe04d | |
|
|
43d7d70aff |
|
|
@ -1 +0,0 @@
|
||||||
build/
|
|
||||||
67
README.md
67
README.md
|
|
@ -1,66 +1,5 @@
|
||||||
# Windows OOBE Scripts
|
# Windows OOBE Script
|
||||||
|
|
||||||
Unattended setup for Windows 10 Enterprise LTSC 2021: an answer file installs Windows, then a PowerShell script configures the machine on first logon and restarts it.
|
First thing to run when logged into windows for the first time.
|
||||||
|
|
||||||
| Variant | Answer file | OOBE script | Disk | ISO |
|
`irm https://url.isworking.fyi/oobe | iex`
|
||||||
|---|---|---|---|---|
|
|
||||||
| VMs (Unraid/KVM, VirtIO) | `unattend/vms-WIPE-DISK.xml` | `oobe-vms.ps1` | **Wipes Disk 0 without asking** | `build/virtio-vms.iso` |
|
|
||||||
| Desktops, laptops, any VM | `unattend/desktop-PROMPT-DISK.xml` | `oobe-desktop.ps1` | Prompts you to pick the disk/partition | `build/desktop.iso` |
|
|
||||||
|
|
||||||
## Run a script by hand
|
|
||||||
|
|
||||||
From an elevated PowerShell:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://url.isworking.fyi/oobe-vms | iex
|
|
||||||
irm https://url.isworking.fyi/oobe-desktop | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
The short links point at the raw files on `master`, so pushing a change updates what new installs run. Logs go to `C:\ProgramData\OOBE\oobe-<name>.log`.
|
|
||||||
|
|
||||||
## What the scripts do
|
|
||||||
|
|
||||||
Both:
|
|
||||||
|
|
||||||
- Install the MeshCentral agent silently as a service
|
|
||||||
- Enable RDP, ping, the `C$` admin share, and OpenSSH Server (PowerShell as the default shell)
|
|
||||||
- Set the network to Private, turn off the UAC prompt, and turn on dark mode with no mouse acceleration (current user and default profile)
|
|
||||||
- Install Winget
|
|
||||||
- Restart when finished
|
|
||||||
|
|
||||||
`oobe-vms.ps1` also:
|
|
||||||
|
|
||||||
- Installs the VirtIO drivers, QEMU guest agent and SPICE agent from the mounted virtio-win ISO (downloads them if it isn't mounted)
|
|
||||||
- Never sleeps or hibernates (enforced by policy), never turns off the display or disks, and stops Windows powering down the network adapter
|
|
||||||
- Enables WinRM and keeps the clock synced
|
|
||||||
|
|
||||||
`oobe-desktop.ps1` also:
|
|
||||||
|
|
||||||
- Uses the Balanced plan: dims the display after 5 minutes (only works on screens Windows controls the brightness of) and sleeps after 30 minutes
|
|
||||||
|
|
||||||
## Build the ISOs
|
|
||||||
|
|
||||||
Requires `curl` and `xorriso`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./build-isos.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This downloads the latest virtio-win ISO (only when a new release is out) and writes to `build/`, which git ignores:
|
|
||||||
|
|
||||||
- `virtio-vms.iso`: virtio-win plus `autounattend.xml` (the VM answer file) and `oobe-vms.ps1`
|
|
||||||
- `desktop.iso`: `autounattend.xml` (the desktop answer file) and `oobe-desktop.ps1`
|
|
||||||
|
|
||||||
Rerun it after changing an answer file. Changes to the scripts reach new installs through the short links without a rebuild. The copy on the ISO is only used if the download fails.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
**VM (Unraid):** put the LTSC ISO in "OS Install ISO" and `virtio-vms.iso` in "VirtIO Drivers ISO", then boot. Everything is automatic, and **Disk 0 is erased**.
|
|
||||||
|
|
||||||
**Desktop:** copy `autounattend.xml` and `oobe-desktop.ps1` from `desktop.iso` to the root of the LTSC install USB. For a VM, attach `desktop.iso` as a second CD instead. Setup stops once to ask where to install; everything else is automatic.
|
|
||||||
|
|
||||||
Setup only reads a file named exactly `autounattend.xml` at the root of a disc or USB drive.
|
|
||||||
|
|
||||||
## Accounts
|
|
||||||
|
|
||||||
Both answer files create a local administrator `silviodante` with the placeholder password `mdwelcome`, stored in plain text. VMs log in automatically every boot; desktops only on the first logon, then show the sign-in screen. Change the password in the answer file before using this for anything that matters.
|
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
function Show-Menu {
|
||||||
|
param (
|
||||||
|
[string]$Title = "ChromaWIN - OOBE and Apps Setup"
|
||||||
|
)
|
||||||
|
|
||||||
|
Clear-Host
|
||||||
|
|
||||||
|
# ASCII Art Title
|
||||||
|
Write-Host @"
|
||||||
|
|
||||||
|
_____ _ __ _______ _ _
|
||||||
|
/ ____| | \ \ / /_ _| \ | |
|
||||||
|
| | | |__ _ __ ___ _ __ ___ __ \ \ /\ / / | | | \| |
|
||||||
|
| | | '_ \| '__/ _ \| '_ ` _ \ / _` \ \/ \/ / | | | . ` |
|
||||||
|
| |____| | | | | | (_) | | | | | | (_| |\ /\ / _| |_| |\ |
|
||||||
|
\_____|_| |_|_| \___/|_| |_| |_|\__,_| \/ \/ |_____|_| \_|
|
||||||
|
|
||||||
|
|
||||||
|
"@ -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Brief description
|
||||||
|
Write-Host "`nEasy and clean OOBE setup. For Chroma domain use ONLY." -ForegroundColor Yellow
|
||||||
|
Write-Host "`nPLEASE READ THE FULL DESCRIPTION. I AM NOT RESPONSIBLE FOR ANY DAMAGE TO YOUR SYSTEM! YOU HAVE BEEN WARNED!" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Longer explanation
|
||||||
|
Write-Host @"
|
||||||
|
|
||||||
|
1: Run the full script.
|
||||||
|
Will configure the following:
|
||||||
|
- Allow ICMP Echo Requests inbound
|
||||||
|
- Enable Dark Mode
|
||||||
|
- Disable Mouse Acceleration
|
||||||
|
- Allow Remote Desktop
|
||||||
|
- Enable c$ directory viewing
|
||||||
|
- Disable UAC prompt
|
||||||
|
- Disable sleep, enable hibernation, and display black after 30 minutes
|
||||||
|
- Enable high performance mode
|
||||||
|
|
||||||
|
Will install the following:
|
||||||
|
- RMM software
|
||||||
|
- Winget
|
||||||
|
- OpenSSH
|
||||||
|
2: Will display System Information
|
||||||
|
3: Just activate Windows.
|
||||||
|
4. Just install apps.
|
||||||
|
|
||||||
|
"@ -ForegroundColor White
|
||||||
|
|
||||||
|
# Draw a line
|
||||||
|
Write-Host "`n----------------------------------------" -ForegroundColor DarkGray
|
||||||
|
|
||||||
|
# Menu options
|
||||||
|
Write-Host "`nAvailable Options:" -ForegroundColor Green
|
||||||
|
Write-Host "1: Full Setup" -ForegroundColor White
|
||||||
|
Write-Host "2: Check System Information" -ForegroundColor White
|
||||||
|
Write-Host "3: Activate Windows" -ForegroundColor White
|
||||||
|
Write-Host "4: Install Apps" -ForegroundColor White
|
||||||
|
Write-Host "5: Exit" -ForegroundColor White
|
||||||
|
|
||||||
|
Write-Host "`n----------------------------------------" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
|
function Run-Full-Script {
|
||||||
|
Write-Host "Running full script..." -ForegroundColor Green
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable ping (ICMP Echo) requests
|
||||||
|
New-NetFirewallRule -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True
|
||||||
|
|
||||||
|
# Turn on Dark Mode
|
||||||
|
New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize -Name AppsUseLightTheme -Value 0 -PropertyType DWORD -Force
|
||||||
|
|
||||||
|
# Disable Mouse Acceleration
|
||||||
|
Set-ItemProperty -Path "HKCU:\Control Panel\Mouse" -Name MouseSensitivity -Value 0
|
||||||
|
|
||||||
|
# Allow Remote Desktop
|
||||||
|
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0
|
||||||
|
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
|
||||||
|
|
||||||
|
# Enable c$
|
||||||
|
Write-Host "Enabling Administrative Shares..." -ForegroundColor Green
|
||||||
|
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "LocalAccountTokenFilterPolicy" -Value 1 -Type DWord -Force
|
||||||
|
Write-Host "Configuring Windows Firewall..." -ForegroundColor Green
|
||||||
|
Enable-NetFirewallRule -DisplayGroup "File and Printer Sharing"
|
||||||
|
Enable-NetFirewallRule -DisplayGroup "Windows Management Instrumentation (WMI)"
|
||||||
|
Write-Host "Enabling SMB File Sharing..." -ForegroundColor Green
|
||||||
|
Enable-WindowsOptionalFeature -Online -FeatureName "SMB1Protocol" -NoRestart
|
||||||
|
Write-Host "Restarting the Server service..." -ForegroundColor Green
|
||||||
|
Restart-Service -Name "LanmanServer" -Force
|
||||||
|
Write-Host "Enabling Network Discovery..." -ForegroundColor Green
|
||||||
|
netsh advfirewall firewall set rule group="Network Discovery" new enable=Yes
|
||||||
|
Write-Host "Verifying C$ administrative share..." -ForegroundColor Green
|
||||||
|
$shares = Get-WmiObject -Class Win32_Share
|
||||||
|
if ($shares | Where-Object { $_.Name -eq "C$" }) {
|
||||||
|
Write-Host "The C$ administrative share is enabled!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Creating C$ administrative share..." -ForegroundColor Yellow
|
||||||
|
$computer = [wmiclass]"Win32_Share"
|
||||||
|
$computer.Create("C:\", "C$", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Disable UAC prompt
|
||||||
|
Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0
|
||||||
|
|
||||||
|
# Download and run application
|
||||||
|
$DownloadPath = "$env:TEMP\app.exe"
|
||||||
|
Invoke-WebRequest -Uri "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0" -OutFile $DownloadPath
|
||||||
|
Start-Process -FilePath $DownloadPath -Wait
|
||||||
|
|
||||||
|
# Run commands in new PowerShell instance
|
||||||
|
# Start-Process powershell.exe -ArgumentList "-NoProfile -Command {irm https://massgrave.dev/get | iex}"
|
||||||
|
# Start-Process powershell.exe -ArgumentList "-NoProfile -Command {irm https://christitus.com/win | iex}"
|
||||||
|
|
||||||
|
# Disable sleep and enable high performance mode, enable hibernation, and display black after 30 minutes
|
||||||
|
powercfg -change -standby-timeout-ac 0
|
||||||
|
powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
|
||||||
|
powercfg /hibernate on
|
||||||
|
powercfg -change -monitor-timeout-ac 30
|
||||||
|
|
||||||
|
# Install Winget
|
||||||
|
#Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe
|
||||||
|
irm https://github.com/asheroto/winget-install/releases/latest/download/winget-install.ps1 | iex
|
||||||
|
|
||||||
|
# Install OpenSSH
|
||||||
|
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
|
||||||
|
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
|
||||||
|
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
||||||
|
|
||||||
|
Start-Service sshd
|
||||||
|
|
||||||
|
Set-Service -Name sshd -StartupType 'Automatic'
|
||||||
|
|
||||||
|
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
|
||||||
|
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
|
||||||
|
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
||||||
|
} else {
|
||||||
|
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SystemInformation {
|
||||||
|
Write-Host "`nGathering system information..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$computerInfo = Get-ComputerInfo
|
||||||
|
Write-Host "`nSystem Information:" -ForegroundColor Green
|
||||||
|
Write-Host "OS: $($computerInfo.OsName) $($computerInfo.OsVersion)" -ForegroundColor White
|
||||||
|
Write-Host "Computer Name: $($computerInfo.CsName)" -ForegroundColor White
|
||||||
|
Write-Host "Manufacturer: $($computerInfo.CsManufacturer)" -ForegroundColor White
|
||||||
|
Write-Host "Model: $($computerInfo.CsModel)" -ForegroundColor White
|
||||||
|
Write-Host "Processor: $($computerInfo.CsProcessors.Name)" -ForegroundColor White
|
||||||
|
Write-Host "Memory: $([math]::Round($computerInfo.CsTotalPhysicalMemory / 1GB, 2)) GB" -ForegroundColor White
|
||||||
|
|
||||||
|
Write-Host "`nPress any key to return to the menu..." -ForegroundColor Gray
|
||||||
|
$null = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
function Activate-Windows {
|
||||||
|
# Set up error handling
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Download the secondary script
|
||||||
|
$secondaryScriptUrl = "https://get.activated.win/"
|
||||||
|
|
||||||
|
# Download the script content
|
||||||
|
$secondaryScript = Invoke-RestMethod -Uri $secondaryScriptUrl -Method Get
|
||||||
|
|
||||||
|
# Important: Execute the script in the current scope to preserve variables and functions
|
||||||
|
# This allows the secondary script's IRM calls to work properly
|
||||||
|
$scriptBlock = [ScriptBlock]::Create($secondaryScript)
|
||||||
|
. $scriptBlock
|
||||||
|
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Error in primary script: $_" -ForegroundColor Red
|
||||||
|
Write-Host "Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Apps {
|
||||||
|
# Set up error handling
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Download the secondary script
|
||||||
|
$secondaryScriptUrl = "https://christitus.com/win"
|
||||||
|
|
||||||
|
# Download the script content
|
||||||
|
$secondaryScript = Invoke-RestMethod -Uri $secondaryScriptUrl -Method Get
|
||||||
|
|
||||||
|
# Important: Execute the script in the current scope to preserve variables and functions
|
||||||
|
# This allows the secondary script's IRM calls to work properly
|
||||||
|
Write-Host "Executing script..." -ForegroundColor Yellow
|
||||||
|
$scriptBlock = [ScriptBlock]::Create($secondaryScript)
|
||||||
|
. $scriptBlock
|
||||||
|
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Error in script: $_" -ForegroundColor Red
|
||||||
|
Write-Host "Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main menu loop
|
||||||
|
do {
|
||||||
|
Show-Menu
|
||||||
|
$selection = Read-Host "`nPlease make a selection"
|
||||||
|
|
||||||
|
switch ($selection) {
|
||||||
|
'1' {
|
||||||
|
Run-Script
|
||||||
|
}
|
||||||
|
'2' {
|
||||||
|
Get-SystemInformation
|
||||||
|
}
|
||||||
|
'3' {
|
||||||
|
Activate-Windows
|
||||||
|
}
|
||||||
|
'4' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Write-Host "`nInvalid selection. Please try again." -ForegroundColor Red
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} until ($selection -eq '4')
|
||||||
|
|
@ -0,0 +1,735 @@
|
||||||
|
################################################################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
################################################################################################
|
||||||
|
|
||||||
|
Param (
|
||||||
|
|
||||||
|
[Parameter(HelpMessage="Work Directory for patch WinRE")][string]$workDir="",
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true,HelpMessage="Path of target package")][string]$packagePath
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Help functions
|
||||||
|
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Log message
|
||||||
|
|
||||||
|
function LogMessage([string]$message)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$message = "$([DateTime]::Now) - $message"
|
||||||
|
|
||||||
|
Write-Host $message
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsTPMBasedProtector
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$DriveLetter = $env:SystemDrive
|
||||||
|
|
||||||
|
LogMessage("Checking BitLocker status")
|
||||||
|
|
||||||
|
$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"
|
||||||
|
|
||||||
|
if(-not $BitLocker)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("No BitLocker object")
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$protectionEnabled = $False
|
||||||
|
|
||||||
|
switch ($BitLocker.GetProtectionStatus().protectionStatus){
|
||||||
|
|
||||||
|
("0"){
|
||||||
|
|
||||||
|
LogMessage("Unprotected")
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
("1"){
|
||||||
|
|
||||||
|
LogMessage("Protected")
|
||||||
|
|
||||||
|
$protectionEnabled = $True
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
("2"){
|
||||||
|
|
||||||
|
LogMessage("Uknown")
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default{
|
||||||
|
|
||||||
|
LogMessage("NoReturn")
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$protectionEnabled)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Bitlocker isn't enabled on the OS")
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID
|
||||||
|
|
||||||
|
$return = $False
|
||||||
|
|
||||||
|
foreach ($ProtectorID in $ProtectorIds){
|
||||||
|
|
||||||
|
$KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType
|
||||||
|
|
||||||
|
switch($KeyProtectorType){
|
||||||
|
|
||||||
|
"1"{
|
||||||
|
|
||||||
|
LogMessage("Trusted Platform Module (TPM)")
|
||||||
|
|
||||||
|
$return = $True
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"4"{
|
||||||
|
|
||||||
|
LogMessage("TPM And PIN")
|
||||||
|
|
||||||
|
$return = $True
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"5"{
|
||||||
|
|
||||||
|
LogMessage("TPM And Startup Key")
|
||||||
|
|
||||||
|
$return = $True
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"6"{
|
||||||
|
|
||||||
|
LogMessage("TPM And PIN And Startup Key")
|
||||||
|
|
||||||
|
$return = $True
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default {break}
|
||||||
|
|
||||||
|
}#endSwitch
|
||||||
|
|
||||||
|
}#EndForeach
|
||||||
|
|
||||||
|
if ($return)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Has TPM-based protector")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Doesn't have TPM-based protector")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetRegistrykeyForSuccess
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
reg add HKLM\SOFTWARE\Microsoft\PushButtonReset /v WinREPathScriptSucceed /d 1 /f
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function TargetfileVersionExam([string]$mountDir)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
# Exam target binary
|
||||||
|
|
||||||
|
$targetBinary=$mountDir + "\Windows\System32\bootmenuux.dll"
|
||||||
|
|
||||||
|
LogMessage("TargetFile: " + $targetBinary)
|
||||||
|
|
||||||
|
$realNTVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($targetBinary).ProductVersion
|
||||||
|
|
||||||
|
$versionString = "$($realNTVersion.Split('.')[0]).$($realNTVersion.Split('.')[1])"
|
||||||
|
|
||||||
|
$fileVersion = $($realNTVersion.Split('.')[2])
|
||||||
|
|
||||||
|
$fileRevision = $($realNTVersion.Split('.')[3])
|
||||||
|
|
||||||
|
LogMessage("Target file version: " + $realNTVersion)
|
||||||
|
|
||||||
|
if (!($versionString -eq "10.0"))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Not Windows 10 or later")
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasUpdated = $False
|
||||||
|
|
||||||
|
#Windows 10, version 1507 10240.19567
|
||||||
|
|
||||||
|
#Windows 10, version 1607 14393.5499
|
||||||
|
|
||||||
|
#Windows 10, version 1809 17763.3646
|
||||||
|
|
||||||
|
#Windows 10, version 2004 1904X.2247
|
||||||
|
|
||||||
|
#Windows 11, version 21H2 22000.1215
|
||||||
|
|
||||||
|
#Windows 11, version 22H2 22621.815
|
||||||
|
|
||||||
|
switch ($fileVersion) {
|
||||||
|
|
||||||
|
"10240" {
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1507")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 19567)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1507 with revision " + $fileRevision + " >= 19567, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"14393" {
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1607")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 5499)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1607 with revision " + $fileRevision + " >= 5499, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"17763" {
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1809")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 3646)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 1809 with revision " + $fileRevision + " >= 3646, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"19041" {
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 2004")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 2247)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 10, version 2004 with revision " + $fileRevision + " >= 2247, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"22000" {
|
||||||
|
|
||||||
|
LogMessage("Windows 11, version 21H2")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 1215)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 11, version 21H2 with revision " + $fileRevision + " >= 1215, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"22621" {
|
||||||
|
|
||||||
|
LogMessage("Windows 11, version 22H2")
|
||||||
|
|
||||||
|
if ($fileRevision -ge 815)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Windows 11, version 22H2 with revision " + $fileRevision + " >= 815, updates have been applied")
|
||||||
|
|
||||||
|
$hasUpdated = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default {
|
||||||
|
|
||||||
|
LogMessage("Warning: unsupported OS version")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hasUpdated
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function PatchPackage([string]$mountDir, [string]$packagePath)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
# Exam target binary
|
||||||
|
|
||||||
|
$hasUpdated = TargetfileVersionExam($mountDir)
|
||||||
|
|
||||||
|
if ($hasUpdated)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("The update has already been added to WinRE")
|
||||||
|
|
||||||
|
SetRegistrykeyForSuccess
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add package
|
||||||
|
|
||||||
|
LogMessage("Apply package:" + $packagePath)
|
||||||
|
|
||||||
|
Dism /Add-Package /Image:$mountDir /PackagePath:$packagePath
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Successfully applied the package")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Applying the package failed with exit code: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup recovery image
|
||||||
|
|
||||||
|
LogMessage("Cleanup image")
|
||||||
|
|
||||||
|
Dism /image:$mountDir /cleanup-image /StartComponentCleanup /ResetBase
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Cleanup image succeed")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Cleanup image failed: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
return $False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Execution starts
|
||||||
|
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Check breadcrumb
|
||||||
|
|
||||||
|
if (Test-Path HKLM:\Software\Microsoft\PushButtonReset)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$values = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset
|
||||||
|
|
||||||
|
if (!(-not $values))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
if (Get-Member -InputObject $values -Name WinREPathScriptSucceed)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$value = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset -Name WinREPathScriptSucceed
|
||||||
|
|
||||||
|
if ($value.WinREPathScriptSucceed -eq 1)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("This script was previously run successfully")
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get WinRE info
|
||||||
|
|
||||||
|
$WinREInfo = Reagentc /info
|
||||||
|
|
||||||
|
$findLocation = $False
|
||||||
|
|
||||||
|
foreach ($line in $WinREInfo)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$params = $line.Split(':')
|
||||||
|
|
||||||
|
if ($params.count -le 1)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params[1].Lenght -eq 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $params[1].Trim()
|
||||||
|
|
||||||
|
if ($content.Lenght -eq 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = $content.IndexOf("\\?\")
|
||||||
|
|
||||||
|
if ($index -ge 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Find \\?\ at " + $index + " for [" + $content + "]")
|
||||||
|
|
||||||
|
$WinRELocation = $content
|
||||||
|
|
||||||
|
$findLocation = $True
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$findLocation)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("WinRE Disabled")
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMessage("WinRE Enabled. WinRE location:" + $WinRELocation)
|
||||||
|
|
||||||
|
$WinREFile = $WinRELocation + "\winre.wim"
|
||||||
|
|
||||||
|
if ([string]::IsNullorEmpty($workDir))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("No input for mount directory")
|
||||||
|
|
||||||
|
LogMessage("Use default path from temporary directory")
|
||||||
|
|
||||||
|
$workDir = [System.IO.Path]::GetTempPath()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMessage("Working Dir: " + $workDir)
|
||||||
|
|
||||||
|
$name = "CA551926-299B-27A55276EC22_Mount"
|
||||||
|
|
||||||
|
$mountDir = Join-Path $workDir $name
|
||||||
|
|
||||||
|
LogMessage("MountDir: " + $mountdir)
|
||||||
|
|
||||||
|
# Delete existing mount directory
|
||||||
|
|
||||||
|
if (Test-Path $mountDir)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Mount directory: " + $mountDir + " already exists")
|
||||||
|
|
||||||
|
LogMessage("Try to unmount it")
|
||||||
|
|
||||||
|
Dism /unmount-image /mountDir:$mountDir /discard
|
||||||
|
|
||||||
|
if (!($LASTEXITCODE -eq 0))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Warning: unmount failed: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMessage("Delete existing mount direcotry " + $mountDir)
|
||||||
|
|
||||||
|
Remove-Item $mountDir -Recurse
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create mount directory
|
||||||
|
|
||||||
|
LogMessage("Create mount directory " + $mountDir)
|
||||||
|
|
||||||
|
New-Item -Path $mountDir -ItemType Directory
|
||||||
|
|
||||||
|
# Set ACL for mount directory
|
||||||
|
|
||||||
|
LogMessage("Set ACL for mount directory")
|
||||||
|
|
||||||
|
icacls $mountDir /inheritance:r
|
||||||
|
|
||||||
|
icacls $mountDir /grant:r SYSTEM:"(OI)(CI)(F)"
|
||||||
|
|
||||||
|
icacls $mountDir /grant:r *S-1-5-32-544:"(OI)(CI)(F)"
|
||||||
|
|
||||||
|
# Mount WinRE
|
||||||
|
|
||||||
|
LogMessage("Mount WinRE:")
|
||||||
|
|
||||||
|
Dism /mount-image /imagefile:$WinREFile /index:1 /mountdir:$mountDir
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
# Patch WinRE
|
||||||
|
|
||||||
|
if (PatchPackage -mountDir $mountDir -packagePath $packagePath)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
$hasUpdated = TargetfileVersionExam($mountDir)
|
||||||
|
|
||||||
|
if ($hasUpdated)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("After patch, find expected version for target file")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Warning: After applying the patch, unexpected version found for the target file")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMessage("Patch succeed, unmount to commit change")
|
||||||
|
|
||||||
|
Dism /unmount-image /mountDir:$mountDir /commit
|
||||||
|
|
||||||
|
if (!($LASTEXITCODE -eq 0))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Unmount failed: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
if ($hasUpdated)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
if (IsTPMBasedProtector)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
# Disable WinRE and re-enable it to let new WinRE be trusted by BitLocker
|
||||||
|
|
||||||
|
LogMessage("Disable WinRE")
|
||||||
|
|
||||||
|
reagentc /disable
|
||||||
|
|
||||||
|
LogMessage("Re-enable WinRE")
|
||||||
|
|
||||||
|
reagentc /enable
|
||||||
|
|
||||||
|
reagentc /info
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Leave a breadcrumb indicates the script has succeed
|
||||||
|
|
||||||
|
SetRegistrykeyForSuccess
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Patch failed or is not applicable, discard unmount")
|
||||||
|
|
||||||
|
Dism /unmount-image /mountDir:$mountDir /discard
|
||||||
|
|
||||||
|
if (!($LASTEXITCODE -eq 0))
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Unmount failed: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
LogMessage("Mount failed: " + $LASTEXITCODE)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup Mount directory in the end
|
||||||
|
|
||||||
|
LogMessage("Delete mount direcotry")
|
||||||
|
|
||||||
|
Remove-Item $mountDir -Recurse
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Builds the answer-file ISOs into build/:
|
|
||||||
# virtio-vms.iso latest virtio-win drivers + unattend/vms-WIPE-DISK.xml (as autounattend.xml) + oobe-vms.ps1
|
|
||||||
# desktop.iso unattend/desktop-PROMPT-DISK.xml (as autounattend.xml) + oobe-desktop.ps1
|
|
||||||
# The virtio-win ISO is only downloaded again when a new release is published.
|
|
||||||
# Requires: curl, xorriso
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
VIRTIO_LATEST=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso
|
|
||||||
BUILD=build
|
|
||||||
STAGE=$BUILD/stage
|
|
||||||
# Joliet names so Windows sees oobe-*.ps1 and autounattend.xml unmangled (volume labels max 16 chars)
|
|
||||||
JOLIET=(-joliet on -compliance joliet_long_names)
|
|
||||||
|
|
||||||
# Root of the ISO as Windows reads it (Joliet, not Rock Ridge)
|
|
||||||
show_root() {
|
|
||||||
echo "== $1"
|
|
||||||
xorriso -report_about WARNING -read_fs norock -indev "$1" -lsl / 2>/dev/null | grep -E -i 'autounattend|oobe-|guest-tools' | awk '{print " ", $NF}'
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir -p "$BUILD"
|
|
||||||
|
|
||||||
# Resolve "latest" to the versioned file; Fedora's redirects drop to plain http, so force https
|
|
||||||
VIRTIO_URL=$(curl -fsSIL -o /dev/null -w '%{url_effective}' "$VIRTIO_LATEST" | sed 's#^http://#https://#')
|
|
||||||
VIRTIO_ISO=$BUILD/$(basename "$VIRTIO_URL")
|
|
||||||
if [[ -f $VIRTIO_ISO ]]; then
|
|
||||||
echo "Using cached $VIRTIO_ISO"
|
|
||||||
else
|
|
||||||
echo "Downloading $VIRTIO_URL"
|
|
||||||
curl -fL --retry 3 -o "$VIRTIO_ISO.part" "$VIRTIO_URL"
|
|
||||||
mv "$VIRTIO_ISO.part" "$VIRTIO_ISO"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# a. VMs: virtio-win + VM answer file + oobe-vms.ps1
|
|
||||||
# Files are added to a copy of the original image rather than extracting and repacking it:
|
|
||||||
# virtio-win stores identical drivers once, and repacking would nearly double its size.
|
|
||||||
rm -f "$BUILD/virtio-vms.iso"
|
|
||||||
xorriso -report_about WARNING -indev "$VIRTIO_ISO" -outdev "$BUILD/virtio-vms.iso" "${JOLIET[@]}" -volid VIRTIO_VMS \
|
|
||||||
-map unattend/vms-WIPE-DISK.xml /autounattend.xml \
|
|
||||||
-map oobe-vms.ps1 /oobe-vms.ps1 \
|
|
||||||
-commit
|
|
||||||
|
|
||||||
# b. Desktops: desktop answer file + oobe-desktop.ps1, no virtio
|
|
||||||
rm -rf "$STAGE" "$BUILD/desktop.iso"
|
|
||||||
mkdir -p "$STAGE"
|
|
||||||
cp unattend/desktop-PROMPT-DISK.xml "$STAGE/autounattend.xml"
|
|
||||||
cp oobe-desktop.ps1 "$STAGE/"
|
|
||||||
xorriso -report_about WARNING -outdev "$BUILD/desktop.iso" "${JOLIET[@]}" -volid OOBE_DESKTOP -map "$STAGE" / -commit
|
|
||||||
rm -rf "$STAGE"
|
|
||||||
show_root "$BUILD/virtio-vms.iso"
|
|
||||||
show_root "$BUILD/desktop.iso"
|
|
||||||
ls -lh "$BUILD"/*.iso
|
|
||||||
164
oobe-desktop.ps1
164
oobe-desktop.ps1
|
|
@ -1,164 +0,0 @@
|
||||||
# OOBE for Windows LTSC desktops and laptops. Runs fully unattended (no prompts).
|
|
||||||
# Pulled by autounattend.xml at first logon, or run by hand:
|
|
||||||
# irm https://url.isworking.fyi/oobe-desktop | iex
|
|
||||||
# Log: C:\ProgramData\OOBE\oobe-desktop.log
|
|
||||||
|
|
||||||
$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0"
|
|
||||||
|
|
||||||
$ErrorActionPreference = 'Continue'
|
|
||||||
# The progress bar makes Invoke-WebRequest extremely slow on Windows PowerShell 5.1
|
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
|
||||||
# Older LTSC builds don't enable TLS 1.2 by default, which breaks GitHub downloads
|
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
||||||
|
|
||||||
$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 {
|
|
||||||
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing
|
|
||||||
return $true
|
|
||||||
} catch {
|
|
||||||
Write-Warning "Download attempt $Attempt of $Uri failed: $_"
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Wait for internet access, the network may not be up yet during first logon
|
|
||||||
function Wait-Network {
|
|
||||||
$MeshHost = ([uri]$MeshAgentUrl).Host
|
|
||||||
$Deadline = (Get-Date).AddMinutes(5)
|
|
||||||
while ((Get-Date) -lt $Deadline) {
|
|
||||||
$Client = New-Object System.Net.Sockets.TcpClient
|
|
||||||
try { if ($Client.ConnectAsync($MeshHost, 443).Wait(5000)) { return } } catch {} finally { $Client.Close() }
|
|
||||||
Write-Output "Waiting for network..."
|
|
||||||
Start-Sleep -Seconds 5
|
|
||||||
}
|
|
||||||
Write-Warning "No network after 5 minutes, continuing anyway."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Balanced plan: dim the display after 5 minutes, sleep after 30 minutes.
|
|
||||||
# Turning the display off stays at the plan default (10 minutes plugged in).
|
|
||||||
$Balanced = '381b4222-f694-41f0-9685-ff5bb260df2e'
|
|
||||||
powercfg /setactive $Balanced
|
|
||||||
|
|
||||||
# "Dim display after" (seconds). Only has a visible effect on screens Windows can
|
|
||||||
# control the brightness of (laptops, all-in-ones); external monitors ignore it.
|
|
||||||
$DisplaySubgroup = '7516b95f-f776-4464-8c53-06167f40cc99'
|
|
||||||
$DimTimeout = '17aaa29b-8b43-4b94-aafe-35f64daaf1ee'
|
|
||||||
powercfg /setacvalueindex $Balanced $DisplaySubgroup $DimTimeout 300
|
|
||||||
powercfg /setdcvalueindex $Balanced $DisplaySubgroup $DimTimeout 300
|
|
||||||
|
|
||||||
powercfg /change standby-timeout-ac 30
|
|
||||||
powercfg /change standby-timeout-dc 30
|
|
||||||
powercfg /setactive $Balanced
|
|
||||||
|
|
||||||
# Treat the network as Private and suppress the "allow this PC to be discoverable" prompt
|
|
||||||
Wait-Network
|
|
||||||
New-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff' -Force | Out-Null
|
|
||||||
Get-NetConnectionProfile | Where-Object NetworkCategory -ne 'DomainAuthenticated' | Set-NetConnectionProfile -NetworkCategory Private
|
|
||||||
|
|
||||||
# Enable ping (ICMP Echo) requests
|
|
||||||
if (-not (Get-NetFirewallRule -Name 'OOBE-ICMPv4-In' -ErrorAction SilentlyContinue)) {
|
|
||||||
New-NetFirewallRule -Name 'OOBE-ICMPv4-In' -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Allow Remote Desktop (firewall groups are referenced by resource ID so this works on non-English installs)
|
|
||||||
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0
|
|
||||||
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28752'
|
|
||||||
|
|
||||||
# Enable c$ (AutoShareWks must be 1; 0 disables the admin shares)
|
|
||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters" /f /v AutoShareWks /t REG_DWORD /d 1
|
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1 /f
|
|
||||||
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28502'
|
|
||||||
|
|
||||||
# Disable UAC prompt
|
|
||||||
Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0
|
|
||||||
|
|
||||||
# Dark Mode and no mouse acceleration, for the current user and the default profile (future users)
|
|
||||||
function Set-UserPreferences($Hive) {
|
|
||||||
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v AppsUseLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v SystemUsesLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
# "Enhance pointer precision" off
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseSpeed /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold1 /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold2 /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
}
|
|
||||||
Set-UserPreferences 'HKCU'
|
|
||||||
$DefaultProfile = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList').Default
|
|
||||||
reg load 'HKU\OOBEDefault' "$DefaultProfile\NTUSER.DAT" | Out-Null
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Set-UserPreferences 'HKU\OOBEDefault'
|
|
||||||
[gc]::Collect()
|
|
||||||
reg unload 'HKU\OOBEDefault' | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install MeshCentral agent silently as a service
|
|
||||||
if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) {
|
|
||||||
Write-Output "Mesh Agent is already installed."
|
|
||||||
} else {
|
|
||||||
$MeshInstaller = Join-Path $WorkDir 'meshagent.exe'
|
|
||||||
if (Save-Download $MeshAgentUrl $MeshInstaller) {
|
|
||||||
# -fullinstall installs and starts the service without showing the install dialog
|
|
||||||
$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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install OpenSSH
|
|
||||||
foreach ($Capability in 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0') {
|
|
||||||
if ((Get-WindowsCapability -Online -Name $Capability).State -ne 'Installed') {
|
|
||||||
Add-WindowsCapability -Online -Name $Capability | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Service -Name sshd -StartupType 'Automatic'
|
|
||||||
Start-Service sshd
|
|
||||||
|
|
||||||
# Use PowerShell instead of cmd.exe for SSH sessions
|
|
||||||
if (Test-Path 'HKLM:\SOFTWARE\OpenSSH') {
|
|
||||||
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
|
|
||||||
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
|
|
||||||
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
|
||||||
} else {
|
|
||||||
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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),
|
|
||||||
# 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) {
|
|
||||||
$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.
|
|
||||||
# Delayed so the script can exit cleanly and the log is flushed before Windows goes down.
|
|
||||||
Write-Output "OOBE complete, restarting in 10 seconds."
|
|
||||||
Stop-Transcript | Out-Null
|
|
||||||
shutdown.exe /r /t 10 /d p:4:1 /c "OOBE complete, restarting to finish installation."
|
|
||||||
236
oobe-vms.ps1
236
oobe-vms.ps1
|
|
@ -1,236 +0,0 @@
|
||||||
# OOBE for Windows LTSC virtual machines (Unraid / KVM with VirtIO). Runs fully unattended (no prompts).
|
|
||||||
# Pulled by autounattend.xml at first logon, or run by hand:
|
|
||||||
# irm https://url.isworking.fyi/oobe-vms | iex
|
|
||||||
# Log: C:\ProgramData\OOBE\oobe-vms.log
|
|
||||||
|
|
||||||
$MeshAgentUrl = "https://rmm.iamchrisama.com/meshagents?id=4&meshid=zxl@U2zM95zh9ZNqah@9mUEjCJ3ptGOE6s5cjsGniacVU1fjRXtVKCKlKJN4aJQW&installflags=0"
|
|
||||||
|
|
||||||
$ErrorActionPreference = 'Continue'
|
|
||||||
# The progress bar makes Invoke-WebRequest extremely slow on Windows PowerShell 5.1
|
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
|
||||||
# Older LTSC builds don't enable TLS 1.2 by default, which breaks GitHub downloads
|
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
||||||
|
|
||||||
$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 {
|
|
||||||
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing
|
|
||||||
return $true
|
|
||||||
} catch {
|
|
||||||
Write-Warning "Download attempt $Attempt of $Uri failed: $_"
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Wait for internet access, the network may not be up yet during first logon
|
|
||||||
function Wait-Network {
|
|
||||||
$MeshHost = ([uri]$MeshAgentUrl).Host
|
|
||||||
$Deadline = (Get-Date).AddMinutes(5)
|
|
||||||
while ((Get-Date) -lt $Deadline) {
|
|
||||||
$Client = New-Object System.Net.Sockets.TcpClient
|
|
||||||
try { if ($Client.ConnectAsync($MeshHost, 443).Wait(5000)) { return } } catch {} finally { $Client.Close() }
|
|
||||||
Write-Output "Waiting for network..."
|
|
||||||
Start-Sleep -Seconds 5
|
|
||||||
}
|
|
||||||
Write-Warning "No network after 5 minutes, continuing anyway."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install VirtIO drivers, QEMU guest agent and SPICE agent from the mounted virtio-win ISO.
|
|
||||||
# This runs first because the network adapter may still need the NetKVM driver.
|
|
||||||
if (Get-Service -Name 'QEMU-GA' -ErrorAction SilentlyContinue) {
|
|
||||||
Write-Output "VirtIO guest tools are already installed."
|
|
||||||
} else {
|
|
||||||
$GuestTools = Get-PSDrive -PSProvider FileSystem | ForEach-Object { Join-Path $_.Root 'virtio-win-guest-tools.exe' } | Where-Object { Test-Path $_ } | Select-Object -First 1
|
|
||||||
|
|
||||||
if ($GuestTools) {
|
|
||||||
Write-Output "Found VirtIO guest tools at $GuestTools"
|
|
||||||
} else {
|
|
||||||
Write-Output "virtio-win ISO not mounted, downloading guest tools..."
|
|
||||||
Wait-Network
|
|
||||||
$GuestTools = Join-Path $WorkDir 'virtio-win-guest-tools.exe'
|
|
||||||
if (-not (Save-Download 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win-guest-tools.exe' $GuestTools)) {
|
|
||||||
$GuestTools = $null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($GuestTools) {
|
|
||||||
# Trust the Red Hat publisher certificate (installer and driver catalogs) so driver installs can't raise a security prompt
|
|
||||||
$Catalogs = Get-ChildItem -Path (Split-Path $GuestTools) -Filter '*.cat' -Recurse -ErrorAction SilentlyContinue | ForEach-Object FullName
|
|
||||||
$Store = New-Object System.Security.Cryptography.X509Certificates.X509Store('TrustedPublisher', 'LocalMachine')
|
|
||||||
$Store.Open('ReadWrite')
|
|
||||||
@($GuestTools) + @($Catalogs) | ForEach-Object { (Get-AuthenticodeSignature $_).SignerCertificate } |
|
|
||||||
Where-Object { $_ -and $_.Subject -match 'Red Hat' } |
|
|
||||||
ForEach-Object { $Store.Add($_) }
|
|
||||||
$Store.Close()
|
|
||||||
|
|
||||||
# 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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disable sleep and hibernation, use high performance mode, never turn off display or disks
|
|
||||||
$HighPerformance = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
|
|
||||||
$Scheme = $HighPerformance
|
|
||||||
if (-not (powercfg /list | Select-String $HighPerformance)) {
|
|
||||||
# The plan is hidden on some builds; recreate it from the built-in template
|
|
||||||
if ("$(powercfg /duplicatescheme $HighPerformance)" -match '[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}') { $Scheme = $Matches[0] }
|
|
||||||
}
|
|
||||||
powercfg /setactive $Scheme
|
|
||||||
|
|
||||||
foreach ($Timeout in 'standby-timeout', 'hibernate-timeout', 'monitor-timeout', 'disk-timeout') {
|
|
||||||
powercfg /change "$Timeout-ac" 0
|
|
||||||
powercfg /change "$Timeout-dc" 0
|
|
||||||
}
|
|
||||||
powercfg /hibernate off
|
|
||||||
|
|
||||||
# Hybrid sleep and the hidden "unattended sleep" timeout (can sleep a VM nobody is logged into)
|
|
||||||
$SleepSubgroup = '238c9fa8-0aad-41ed-83f4-97be242c8f20'
|
|
||||||
foreach ($Setting in '94ac6d29-73ce-41a6-809f-6363ba21b47e', '7bc4a2f9-d8fc-4469-b07b-33eb785aaca0') {
|
|
||||||
powercfg /setacvalueindex SCHEME_CURRENT $SleepSubgroup $Setting 0
|
|
||||||
powercfg /setdcvalueindex SCHEME_CURRENT $SleepSubgroup $Setting 0
|
|
||||||
}
|
|
||||||
powercfg /setactive SCHEME_CURRENT
|
|
||||||
|
|
||||||
# Enforce through policy so a plan change or feature update can't bring sleep back
|
|
||||||
$PowerPolicies = @(
|
|
||||||
'29f6c1db-86da-48c5-9fdb-f2b67b1f44da' # Sleep timeout = never
|
|
||||||
'9d7815a6-7ee4-497e-8888-515a05f02364' # Hibernate timeout = never
|
|
||||||
'7bc4a2f9-d8fc-4469-b07b-33eb785aaca0' # Unattended sleep timeout = never
|
|
||||||
'94ac6d29-73ce-41a6-809f-6363ba21b47e' # Hybrid sleep = off
|
|
||||||
'abfc2519-3608-4c2a-94ea-171b0ed546ab' # Allow standby states (S1-S3) = off, removes Sleep from the power menu
|
|
||||||
)
|
|
||||||
foreach ($Policy in $PowerPolicies) {
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Power\PowerSettings\$Policy" /v ACSettingIndex /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
reg add "HKLM\SOFTWARE\Policies\Microsoft\Power\PowerSettings\$Policy" /v DCSettingIndex /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
}
|
|
||||||
# Disable Fast Startup
|
|
||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Power" /v HiberbootEnabled /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
|
|
||||||
# Don't let Windows power down the network adapter
|
|
||||||
Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Disable-NetAdapterPowerManagement -NoRestart -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
# Treat the network as Private and suppress the "allow this PC to be discoverable" prompt
|
|
||||||
Wait-Network
|
|
||||||
New-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff' -Force | Out-Null
|
|
||||||
Get-NetConnectionProfile | Where-Object NetworkCategory -ne 'DomainAuthenticated' | Set-NetConnectionProfile -NetworkCategory Private
|
|
||||||
|
|
||||||
# Enable ping (ICMP Echo) requests
|
|
||||||
if (-not (Get-NetFirewallRule -Name 'OOBE-ICMPv4-In' -ErrorAction SilentlyContinue)) {
|
|
||||||
New-NetFirewallRule -Name 'OOBE-ICMPv4-In' -DisplayName "Allow ICMP Echo Request" -Protocol ICMPv4 -IcmpType 8 -Enabled True | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Allow Remote Desktop (firewall groups are referenced by resource ID so this works on non-English installs)
|
|
||||||
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0
|
|
||||||
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28752'
|
|
||||||
|
|
||||||
# Enable c$ (AutoShareWks must be 1; 0 disables the admin shares)
|
|
||||||
reg add "HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters" /f /v AutoShareWks /t REG_DWORD /d 1
|
|
||||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v "LocalAccountTokenFilterPolicy" /t REG_DWORD /d 1 /f
|
|
||||||
Enable-NetFirewallRule -Group '@FirewallAPI.dll,-28502'
|
|
||||||
|
|
||||||
# Disable UAC prompt
|
|
||||||
Set-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows\CurrentVersion\policies\system' -Name "ConsentPromptBehaviorAdmin" -Value 0
|
|
||||||
|
|
||||||
# Enable WinRM / PowerShell remoting
|
|
||||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck | Out-Null
|
|
||||||
|
|
||||||
# Keep the clock in sync (w32time is trigger-start only on workgroup machines)
|
|
||||||
Set-Service -Name w32time -StartupType Automatic
|
|
||||||
Start-Service -Name w32time
|
|
||||||
w32tm /resync /force | Out-Null
|
|
||||||
|
|
||||||
# Dark Mode and no mouse acceleration, for the current user and the default profile (future users)
|
|
||||||
function Set-UserPreferences($Hive) {
|
|
||||||
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v AppsUseLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" /v SystemUsesLightTheme /t REG_DWORD /d 0 /f | Out-Null
|
|
||||||
# "Enhance pointer precision" off
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseSpeed /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold1 /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
reg add "$Hive\Control Panel\Mouse" /v MouseThreshold2 /t REG_SZ /d 0 /f | Out-Null
|
|
||||||
}
|
|
||||||
Set-UserPreferences 'HKCU'
|
|
||||||
$DefaultProfile = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList').Default
|
|
||||||
reg load 'HKU\OOBEDefault' "$DefaultProfile\NTUSER.DAT" | Out-Null
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Set-UserPreferences 'HKU\OOBEDefault'
|
|
||||||
[gc]::Collect()
|
|
||||||
reg unload 'HKU\OOBEDefault' | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install MeshCentral agent silently as a service
|
|
||||||
if (Get-Service -Name 'Mesh Agent' -ErrorAction SilentlyContinue) {
|
|
||||||
Write-Output "Mesh Agent is already installed."
|
|
||||||
} else {
|
|
||||||
$MeshInstaller = Join-Path $WorkDir 'meshagent.exe'
|
|
||||||
if (Save-Download $MeshAgentUrl $MeshInstaller) {
|
|
||||||
# -fullinstall installs and starts the service without showing the install dialog
|
|
||||||
$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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Install OpenSSH
|
|
||||||
foreach ($Capability in 'OpenSSH.Client~~~~0.0.1.0', 'OpenSSH.Server~~~~0.0.1.0') {
|
|
||||||
if ((Get-WindowsCapability -Online -Name $Capability).State -ne 'Installed') {
|
|
||||||
Add-WindowsCapability -Online -Name $Capability | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Service -Name sshd -StartupType 'Automatic'
|
|
||||||
Start-Service sshd
|
|
||||||
|
|
||||||
# Use PowerShell instead of cmd.exe for SSH sessions
|
|
||||||
if (Test-Path 'HKLM:\SOFTWARE\OpenSSH') {
|
|
||||||
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
|
|
||||||
Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
|
|
||||||
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
|
||||||
} else {
|
|
||||||
Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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),
|
|
||||||
# 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) {
|
|
||||||
$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.
|
|
||||||
# Delayed so the script can exit cleanly and the log is flushed before Windows goes down.
|
|
||||||
Write-Output "OOBE complete, restarting in 10 seconds."
|
|
||||||
Stop-Transcript | Out-Null
|
|
||||||
shutdown.exe /r /t 10 /d p:4:1 /c "OOBE complete, restarting to finish installation."
|
|
||||||
|
|
@ -1,334 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
============================================================================
|
|
||||||
autounattend.xml - Windows 10 Enterprise LTSC 2021 (21H2), x64
|
|
||||||
Target: Physical desktops/laptops AND virtual machines, UEFI or BIOS
|
|
||||||
Storage: Any. VirtIO drivers load automatically if a virtio-win ISO is
|
|
||||||
attached; otherwise use "Load driver" on the disk screen.
|
|
||||||
Behavior: STOPS at "Where do you want to install Windows?" so you pick
|
|
||||||
(and delete/format) partitions yourself - nothing is wiped
|
|
||||||
without you. Then installs, autologons ONCE, installs VirtIO
|
|
||||||
guest tools if a virtio-win disc is present, and downloads and
|
|
||||||
runs oobe-desktop.ps1 from https://url.isworking.fyi/oobe-desktop.
|
|
||||||
|
|
||||||
BUILD: ./build-isos.sh produces build/desktop.iso with this file as
|
|
||||||
autounattend.xml plus oobe-desktop.ps1 (offline fallback).
|
|
||||||
VM: attach it as a second CD next to the LTSC ISO.
|
|
||||||
PC: copy autounattend.xml and oobe-desktop.ps1 to the root of
|
|
||||||
the LTSC install USB (or a second USB stick).
|
|
||||||
|
|
||||||
Credentials baked in below: silviodante / mdwelcome (stored in CLEAR TEXT)
|
|
||||||
Search for "EDIT" to find every value you probably want to change.
|
|
||||||
============================================================================
|
|
||||||
-->
|
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 1: windowsPE
|
|
||||||
======================================================================= -->
|
|
||||||
<settings pass="windowsPE">
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-International-Core-WinPE"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<SetupUILanguage>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
</SetupUILanguage>
|
|
||||||
<InputLocale>0409:00000409</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
VIRTIO DRIVER INJECTION - the load-bearing part.
|
|
||||||
WinPE has no viostor/vioscsi driver, so without this the vdisk is
|
|
||||||
invisible on the disk screen. On physical PCs these paths don't
|
|
||||||
exist and are skipped; the disk screen's Load Driver button is
|
|
||||||
still available for anything else (RAID/VMD/NVMe controllers).
|
|
||||||
|
|
||||||
Drive letters in WinPE are not deterministic: the LTSC disc and the
|
|
||||||
VirtIO disc land on D:/E: in either order, so both are listed, plus
|
|
||||||
F: as insurance. Setup logs a warning for paths that don't exist
|
|
||||||
and continues, so the extra entries cost nothing.
|
|
||||||
|
|
||||||
viostor = VirtIO block bus (Unraid vdisk bus: virtio)
|
|
||||||
vioscsi = VirtIO SCSI bus (Unraid vdisk bus: virtio-scsi)
|
|
||||||
NetKVM = network, so WinPE has connectivity if you ever need it
|
|
||||||
=============================================================== -->
|
|
||||||
<component name="Microsoft-Windows-PnpCustomizationsWinPE"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<DriverPaths>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
|
||||||
<Path>D:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
|
||||||
<Path>E:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
|
|
||||||
<Path>F:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
|
|
||||||
<Path>D:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
|
|
||||||
<Path>E:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="6">
|
|
||||||
<Path>F:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="7">
|
|
||||||
<Path>D:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="8">
|
|
||||||
<Path>E:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="9">
|
|
||||||
<Path>F:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
</DriverPaths>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Setup"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
|
|
||||||
<!-- No DiskConfiguration and no InstallTo on purpose: Setup prompts
|
|
||||||
for the target disk/partition, which is the only screen shown. -->
|
|
||||||
|
|
||||||
<ImageInstall>
|
|
||||||
<OSImage>
|
|
||||||
<InstallFrom>
|
|
||||||
<MetaData wcm:action="add">
|
|
||||||
<Key>/IMAGE/INDEX</Key>
|
|
||||||
<Value>1</Value>
|
|
||||||
</MetaData>
|
|
||||||
</InstallFrom>
|
|
||||||
<WillShowUI>OnError</WillShowUI>
|
|
||||||
</OSImage>
|
|
||||||
</ImageInstall>
|
|
||||||
|
|
||||||
<UserData>
|
|
||||||
<!-- EDIT: generic KMS client key for Enterprise LTSC 2021. Gets Setup
|
|
||||||
past the prompt; does NOT activate. N edition is
|
|
||||||
92NFX-8DJQP-P6BBQ-THF9C-7CG2H. -->
|
|
||||||
<ProductKey>
|
|
||||||
<Key>M7XTQ-FN8P6-TTKYV-9D4CC-J462D</Key>
|
|
||||||
<WillShowUI>OnError</WillShowUI>
|
|
||||||
</ProductKey>
|
|
||||||
<AcceptEula>true</AcceptEula>
|
|
||||||
<FullName>Silvio Dante</FullName>
|
|
||||||
<Organization>Bada Bing!</Organization>
|
|
||||||
</UserData>
|
|
||||||
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 2: specialize
|
|
||||||
======================================================================= -->
|
|
||||||
<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"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<!-- "*" = random name. A literal name must NOT match any local account. -->
|
|
||||||
<ComputerName>*</ComputerName>
|
|
||||||
<!-- EDIT: `tzutil /l` lists every valid value. -->
|
|
||||||
<TimeZone>Pacific Standard Time</TimeZone>
|
|
||||||
<RegisteredOwner>Silvio Dante</RegisteredOwner>
|
|
||||||
<RegisteredOrganization>Bada Bing!</RegisteredOrganization>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Security-SPP-UX"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<SkipAutoActivation>true</SkipAutoActivation>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<!-- RDP on (NLA required) so the machine is reachable remotely. -->
|
|
||||||
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<fDenyTSConnections>false</fDenyTSConnections>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<UserAuthentication>1</UserAuthentication>
|
|
||||||
<SecurityLayer>2</SecurityLayer>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 3: oobeSystem
|
|
||||||
======================================================================= -->
|
|
||||||
<settings pass="oobeSystem">
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-International-Core"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<InputLocale>0409:00000409</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Shell-Setup"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
|
|
||||||
<OOBE>
|
|
||||||
<HideEULAPage>true</HideEULAPage>
|
|
||||||
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
|
||||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
|
||||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
|
||||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
|
||||||
<NetworkLocation>Work</NetworkLocation>
|
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
|
||||||
</OOBE>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
ACCOUNTS - password is mdwelcome, in clear text, three times.
|
|
||||||
Treat this file and the merged ISO as secrets.
|
|
||||||
=============================================================== -->
|
|
||||||
<UserAccounts>
|
|
||||||
<AdministratorPassword>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</AdministratorPassword>
|
|
||||||
|
|
||||||
<LocalAccounts>
|
|
||||||
<LocalAccount wcm:action="add">
|
|
||||||
<Name>silviodante</Name>
|
|
||||||
<DisplayName>Silvio Dante</DisplayName>
|
|
||||||
<Description>Local administrator for automated builds</Description>
|
|
||||||
<Group>Administrators</Group>
|
|
||||||
<Password>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
</LocalAccount>
|
|
||||||
</LocalAccounts>
|
|
||||||
</UserAccounts>
|
|
||||||
|
|
||||||
<!-- Autologon ONCE, just long enough for the FirstLogonCommands. After
|
|
||||||
oobe-desktop.ps1 restarts the PC it lands on the sign-in screen. -->
|
|
||||||
<AutoLogon>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<Username>silviodante</Username>
|
|
||||||
<LogonCount>1</LogonCount>
|
|
||||||
<Password>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
</AutoLogon>
|
|
||||||
|
|
||||||
<DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
FIRST LOGON COMMANDS - run once, elevated, as silviodante.
|
|
||||||
=============================================================== -->
|
|
||||||
<FirstLogonCommands>
|
|
||||||
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<Description>Show file extensions</Description>
|
|
||||||
<CommandLine>reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine>
|
|
||||||
<RequiresUserInput>false</RequiresUserInput>
|
|
||||||
</SynchronousCommand>
|
|
||||||
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>2</Order>
|
|
||||||
<Description>Allow signed PowerShell scripts</Description>
|
|
||||||
<CommandLine>powershell -NoProfile -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force"</CommandLine>
|
|
||||||
<RequiresUserInput>false</RequiresUserInput>
|
|
||||||
</SynchronousCommand>
|
|
||||||
|
|
||||||
<!-- VMs only: if a virtio-win disc is attached, install the guest
|
|
||||||
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. 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) { $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 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 "$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>
|
|
||||||
|
|
||||||
</FirstLogonCommands>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
</unattend>
|
|
||||||
|
|
||||||
|
|
@ -1,381 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
============================================================================
|
|
||||||
autounattend.xml - Windows 10 Enterprise LTSC 2021 (21H2), x64
|
|
||||||
Target: Unraid / KVM virtual machine, UEFI firmware, GPT disk
|
|
||||||
Storage: VirtIO (or VirtIO-SCSI) vdisk - drivers injected into WinPE
|
|
||||||
Behavior: Wipes Disk 0, creates ESP/MSR/Windows, installs, autologons,
|
|
||||||
installs the VirtIO guest tools, then downloads and runs
|
|
||||||
oobe-vms.ps1 from https://url.isworking.fyi/oobe-vms.
|
|
||||||
|
|
||||||
BUILD: ./build-isos.sh produces build/virtio-vms.iso with this file as
|
|
||||||
autounattend.xml plus oobe-vms.ps1 (offline fallback) at the
|
|
||||||
root of the latest virtio-win ISO. Attach that ISO in the Unraid VM's
|
|
||||||
"VirtIO Drivers ISO" slot; leave "OS Install ISO" on the LTSC image.
|
|
||||||
|
|
||||||
>>> DESTRUCTIVE: WillWipeDisk erases Disk 0 with no prompt. <<<
|
|
||||||
|
|
||||||
Credentials baked in below: silviodante / mdwelcome (stored in CLEAR TEXT)
|
|
||||||
Search for "EDIT" to find every value you probably want to change.
|
|
||||||
============================================================================
|
|
||||||
-->
|
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 1: windowsPE
|
|
||||||
======================================================================= -->
|
|
||||||
<settings pass="windowsPE">
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-International-Core-WinPE"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<SetupUILanguage>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
</SetupUILanguage>
|
|
||||||
<InputLocale>0409:00000409</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
VIRTIO DRIVER INJECTION - the load-bearing part.
|
|
||||||
WinPE has no viostor/vioscsi driver, so without this the vdisk is
|
|
||||||
invisible and Setup dies with "no drives were found". Because the
|
|
||||||
disk layout is automated there is no Load Driver button to fall
|
|
||||||
back on, so this must succeed.
|
|
||||||
|
|
||||||
Drive letters in WinPE are not deterministic: the LTSC disc and the
|
|
||||||
VirtIO disc land on D:/E: in either order, so both are listed, plus
|
|
||||||
F: as insurance. Setup logs a warning for paths that don't exist
|
|
||||||
and continues, so the extra entries cost nothing.
|
|
||||||
|
|
||||||
viostor = VirtIO block bus (Unraid vdisk bus: virtio)
|
|
||||||
vioscsi = VirtIO SCSI bus (Unraid vdisk bus: virtio-scsi)
|
|
||||||
NetKVM = network, so WinPE has connectivity if you ever need it
|
|
||||||
=============================================================== -->
|
|
||||||
<component name="Microsoft-Windows-PnpCustomizationsWinPE"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<DriverPaths>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
|
||||||
<Path>D:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
|
||||||
<Path>E:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
|
|
||||||
<Path>F:\viostor\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
|
|
||||||
<Path>D:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
|
|
||||||
<Path>E:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="6">
|
|
||||||
<Path>F:\vioscsi\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="7">
|
|
||||||
<Path>D:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="8">
|
|
||||||
<Path>E:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="9">
|
|
||||||
<Path>F:\NetKVM\w10\amd64</Path>
|
|
||||||
</PathAndCredentials>
|
|
||||||
</DriverPaths>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Setup"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
|
|
||||||
<!-- Standard UEFI/GPT layout: ESP 260MB / MSR 16MB / Windows rest -->
|
|
||||||
<DiskConfiguration>
|
|
||||||
<WillShowUI>OnError</WillShowUI>
|
|
||||||
<Disk wcm:action="add">
|
|
||||||
<DiskID>0</DiskID>
|
|
||||||
<WillWipeDisk>true</WillWipeDisk>
|
|
||||||
|
|
||||||
<CreatePartitions>
|
|
||||||
<CreatePartition wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<Type>EFI</Type>
|
|
||||||
<Size>260</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>
|
|
||||||
<Letter>C</Letter>
|
|
||||||
<Format>NTFS</Format>
|
|
||||||
</ModifyPartition>
|
|
||||||
</ModifyPartitions>
|
|
||||||
</Disk>
|
|
||||||
</DiskConfiguration>
|
|
||||||
|
|
||||||
<ImageInstall>
|
|
||||||
<OSImage>
|
|
||||||
<InstallFrom>
|
|
||||||
<MetaData wcm:action="add">
|
|
||||||
<Key>/IMAGE/INDEX</Key>
|
|
||||||
<Value>1</Value>
|
|
||||||
</MetaData>
|
|
||||||
</InstallFrom>
|
|
||||||
<InstallTo>
|
|
||||||
<DiskID>0</DiskID>
|
|
||||||
<PartitionID>3</PartitionID>
|
|
||||||
</InstallTo>
|
|
||||||
<InstallToAvailablePartition>false</InstallToAvailablePartition>
|
|
||||||
<WillShowUI>OnError</WillShowUI>
|
|
||||||
</OSImage>
|
|
||||||
</ImageInstall>
|
|
||||||
|
|
||||||
<UserData>
|
|
||||||
<!-- EDIT: generic KMS client key for Enterprise LTSC 2021. Gets Setup
|
|
||||||
past the prompt; does NOT activate. N edition is
|
|
||||||
92NFX-8DJQP-P6BBQ-THF9C-7CG2H. -->
|
|
||||||
<ProductKey>
|
|
||||||
<Key>M7XTQ-FN8P6-TTKYV-9D4CC-J462D</Key>
|
|
||||||
<WillShowUI>OnError</WillShowUI>
|
|
||||||
</ProductKey>
|
|
||||||
<AcceptEula>true</AcceptEula>
|
|
||||||
<FullName>Silvio Dante</FullName>
|
|
||||||
<Organization>Bada Bing!</Organization>
|
|
||||||
</UserData>
|
|
||||||
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 2: specialize
|
|
||||||
======================================================================= -->
|
|
||||||
<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"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<!-- "*" = random name. A literal name must NOT match any local account. -->
|
|
||||||
<ComputerName>*</ComputerName>
|
|
||||||
<!-- EDIT: `tzutil /l` lists every valid value. -->
|
|
||||||
<TimeZone>Pacific Standard Time</TimeZone>
|
|
||||||
<RegisteredOwner>Silvio Dante</RegisteredOwner>
|
|
||||||
<RegisteredOrganization>Bada Bing!</RegisteredOrganization>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Security-SPP-UX"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<SkipAutoActivation>true</SkipAutoActivation>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<!-- RDP on - the practical way into a headless Unraid VM once the
|
|
||||||
VirtIO video driver replaces the VNC console. -->
|
|
||||||
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<fDenyTSConnections>false</fDenyTSConnections>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<UserAuthentication>1</UserAuthentication>
|
|
||||||
<SecurityLayer>2</SecurityLayer>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
<!-- =======================================================================
|
|
||||||
PASS 3: oobeSystem
|
|
||||||
======================================================================= -->
|
|
||||||
<settings pass="oobeSystem">
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-International-Core"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
<InputLocale>0409:00000409</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
</component>
|
|
||||||
|
|
||||||
<component name="Microsoft-Windows-Shell-Setup"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
publicKeyToken="31bf3856ad364e35"
|
|
||||||
language="neutral"
|
|
||||||
versionScope="nonSxS"
|
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
|
||||||
|
|
||||||
<OOBE>
|
|
||||||
<HideEULAPage>true</HideEULAPage>
|
|
||||||
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
|
||||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
|
||||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
|
||||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
|
||||||
<NetworkLocation>Work</NetworkLocation>
|
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
|
||||||
</OOBE>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
ACCOUNTS - password is mdwelcome, in clear text, three times.
|
|
||||||
Treat this file and the merged ISO as secrets.
|
|
||||||
=============================================================== -->
|
|
||||||
<UserAccounts>
|
|
||||||
<AdministratorPassword>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</AdministratorPassword>
|
|
||||||
|
|
||||||
<LocalAccounts>
|
|
||||||
<LocalAccount wcm:action="add">
|
|
||||||
<Name>silviodante</Name>
|
|
||||||
<DisplayName>Silvio Dante</DisplayName>
|
|
||||||
<Description>Local administrator for automated VM builds</Description>
|
|
||||||
<Group>Administrators</Group>
|
|
||||||
<Password>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
</LocalAccount>
|
|
||||||
</LocalAccounts>
|
|
||||||
</UserAccounts>
|
|
||||||
|
|
||||||
<!-- Persistent autologon. Drop LogonCount to 1 if you'd rather the VM
|
|
||||||
land on a logon screen once provisioning finishes. -->
|
|
||||||
<AutoLogon>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<Username>silviodante</Username>
|
|
||||||
<LogonCount>999</LogonCount>
|
|
||||||
<Password>
|
|
||||||
<Value>mdwelcome</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
</AutoLogon>
|
|
||||||
|
|
||||||
<DisableAutoDaylightTimeSet>false</DisableAutoDaylightTimeSet>
|
|
||||||
|
|
||||||
<!-- ===============================================================
|
|
||||||
FIRST LOGON COMMANDS - run once, elevated, as silviodante.
|
|
||||||
=============================================================== -->
|
|
||||||
<FirstLogonCommands>
|
|
||||||
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>1</Order>
|
|
||||||
<Description>Show file extensions</Description>
|
|
||||||
<CommandLine>reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine>
|
|
||||||
<RequiresUserInput>false</RequiresUserInput>
|
|
||||||
</SynchronousCommand>
|
|
||||||
|
|
||||||
<SynchronousCommand wcm:action="add">
|
|
||||||
<Order>2</Order>
|
|
||||||
<Description>Allow signed PowerShell scripts</Description>
|
|
||||||
<CommandLine>powershell -NoProfile -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force"</CommandLine>
|
|
||||||
<RequiresUserInput>false</RequiresUserInput>
|
|
||||||
</SynchronousCommand>
|
|
||||||
|
|
||||||
<!-- 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. 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) { $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 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 "$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>
|
|
||||||
|
|
||||||
</FirstLogonCommands>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
|
|
||||||
</unattend>
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue