Compare commits

..
10 Commits
Author SHA1 Message Date
uhlwoogi f60060140e Create ESP-McFaddin-Code
Used on the ESP to program Samsung DMB's

 - Board: ESP32S3 Dev Module
    - USB Mode: USB-OTG (TinyUSB)
    - USB CDC On Boot: Disabled
    - Upload Mode: UART0 / Hardware CDC
    - Flash Size: 16MB (128Mb)
    - PSRAM: OPI PSRAM
    - Partition Scheme: 16M Flash (3MB APP/9.9MB FATFS)
    - Port: /dev/ttyACM0
2026-06-20 18:33:07 -05:00
uhlwoogi 457b2dc8aa Create WinPEScript 2026-05-17 08:40:05 -05:00
uhlwoogiandClaude Sonnet 4.6 ad2dcf3b14 Add Headscale registry keys before service restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 01:09:54 +00:00
uhlwoogiandClaude Sonnet 4.6 f26052308c Fix ProgramFiles(x86) env var and wipe full ProgramData Tailscale folder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 01:07:06 +00:00
uhlwoogiandClaude Sonnet 4.6 dd36fe4476 Add Tailscale reset script for ESA/Headscale environments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 01:02:54 +00:00
uhlwoogi 98e703d100 Create Install-RustDesk-Basic-Tadco.ps1 2026-04-30 16:31:28 -05:00
uhlwoogi 22fde95381 Update and rename Install-RustDesk-Basic-Tadco.ps1 to Install-RustDesk-Basic-Pinnacle.ps1 2026-04-30 16:00:22 -05:00
uhlwoogi eba65438ec Update Install-RustDesk-Basic-Tadco.ps1 2026-04-30 15:55:45 -05:00
uhlwoogi d145ace0ce Create Install-RustDesk-Basic-Tadco.ps1 2026-04-30 15:49:23 -05:00
uhlwoogi a4e5c588e8 Rename Install-RustDesk-Basic.ps1 to Install-RustDesk-Basic-eStudio.ps1 2026-04-30 15:48:53 -05:00
6 changed files with 1107 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# PowerShell Script to Reset Tailscale on Windows
# Works even if Tailscale service is not present
function Get-TailscalePath {
$pathsToCheck = @(
"$env:ProgramFiles\Tailscale\tailscale.exe",
"${env:ProgramFiles(x86)}\Tailscale\tailscale.exe",
"$env:LOCALAPPDATA\Tailscale\tailscale.exe"
)
foreach ($path in $pathsToCheck) {
if (Test-Path $path) {
return $path
}
}
return $null
}
$tailscalePath = Get-TailscalePath
if (-not $tailscalePath) {
Write-Warning "Tailscale CLI not found on this system. Skipping CLI commands..."
} else {
Write-Host "Found Tailscale CLI at $tailscalePath"
& "$tailscalePath" logout
}
# Stop service if exists
$service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
if ($service) {
Write-Host "Stopping Tailscale service..."
Stop-Service Tailscale -Force
} else {
Write-Warning "Tailscale service not found, skipping stop."
}
# Remove ProgramData folder if present
$programDataPath = "C:\ProgramData\Tailscale"
if (Test-Path $programDataPath) {
Remove-Item $programDataPath -Recurse -Force
Write-Host "Removed folder: $programDataPath"
}
# Remove appdata folders
$localApp = "$env:LOCALAPPDATA\Tailscale"
$roamingApp = "$env:APPDATA\Tailscale"
foreach ($folder in @($localApp, $roamingApp)) {
if (Test-Path $folder) {
Remove-Item $folder -Recurse -Force
Write-Host "Removed folder: $folder"
}
}
# Set Headscale registry configuration
$registryPath = 'HKLM:\Software\Tailscale IPN'
New-Item -Path $registryPath -Force | Out-Null
New-ItemProperty -Path $registryPath -Name UnattendedMode -PropertyType String -Value "user-decides" -Force | Out-Null
New-ItemProperty -Path $registryPath -Name AllowIncomingConnections -PropertyType String -Value "never" -Force | Out-Null
New-ItemProperty -Path $registryPath -Name AdminConsole -PropertyType String -Value "hide" -Force | Out-Null
New-ItemProperty -Path $registryPath -Name NetworkDevices -PropertyType String -Value "hide" -Force | Out-Null
New-ItemProperty -Path $registryPath -Name TestMenu -PropertyType String -Value "hide" -Force | Out-Null
New-ItemProperty -Path $registryPath -Name LoginURL -PropertyType String -Value "http://headscale.estudiogroup.com:8080" -Force | Out-Null
Write-Host "Registry keys created successfully."
# Restart service if exists
if ($service) {
Start-Service Tailscale
Write-Host "Restarted Tailscale service"
}
# Show prefs if CLI was found
if ($tailscalePath) {
& "$tailscalePath" debug prefs
}
Write-Host "`n✅ Tailscale reset complete. If you're using Headscale, run:"
Write-Host "`ttailscale up --login-server http://headscale.estudiogroup.com:8080"
+177
View File
@@ -0,0 +1,177 @@
// =============================================
// Samsung QB Series - Wisar Digital Setup
// ESP32-S3 DevKit N16R8 - USB HID Keyboard
// =============================================
// Navigates OSD menus to configure URL Launcher
// pointing to Wisar Digital signage platform.
// Plug ESP32 USB-C (OTG) port into QB display USB port.
// Display should be powered on at home/menu screen.
// =============================================
//
// =============================================
// AVAILABLE HID KEYS REFERENCE
// =============================================
//
// --- Modifier Keys ---
// KEY_LEFT_CTRL KEY_RIGHT_CTRL
// KEY_LEFT_SHIFT KEY_RIGHT_SHIFT
// KEY_LEFT_ALT KEY_RIGHT_ALT
// KEY_LEFT_GUI KEY_RIGHT_GUI (Windows/Super/Cmd)
//
// --- Navigation Keys ---
// KEY_UP_ARROW KEY_DOWN_ARROW
// KEY_LEFT_ARROW KEY_RIGHT_ARROW
// KEY_HOME KEY_END
// KEY_PAGE_UP KEY_PAGE_DOWN
//
// --- Editing Keys ---
// KEY_BACKSPACE KEY_TAB
// KEY_RETURN (Enter)
// KEY_ESC
// KEY_INSERT KEY_DELETE
// KEY_CAPS_LOCK KEY_NUM_LOCK
// KEY_SCROLL_LOCK KEY_PRINT_SCREEN
// KEY_PAUSE
//
// --- Function Keys ---
// KEY_F1 KEY_F2 KEY_F3 KEY_F4
// KEY_F5 KEY_F6 KEY_F7 KEY_F8
// KEY_F9 KEY_F10 KEY_F11 KEY_F12
// KEY_F13 KEY_F14 KEY_F15 KEY_F16
// KEY_F17 KEY_F18 KEY_F19 KEY_F20
// KEY_F21 KEY_F22 KEY_F23 KEY_F24
//
// --- Printable Characters ---
// Pass directly to write() or print():
// Letters: 'a'-'z', 'A'-'Z'
// Numbers: '0'-'9'
// Symbols: '!','@','#','$','%','^','&','*','(',')'
// '-','=','[',']','\\',';','\'',',','.','/','`'
// '_','+','{','}','|',':','"','<','>','?','~'
// ' ' (space)
//
// --- Modifier Combos ---
// Use press() for combos, e.g.:
// Keyboard.press(KEY_LEFT_CTRL);
// Keyboard.press('c'); // Ctrl+C
// Keyboard.releaseAll();
//
// --- Menu / Media Keys (USBHIDConsumerControl) ---
// Requires #include "USBHIDConsumerControl.h"
// Not used in this sketch but available:
// CONSUMER_CONTROL_VOLUME_INCREMENT
// CONSUMER_CONTROL_VOLUME_DECREMENT
// CONSUMER_CONTROL_MUTE
// CONSUMER_CONTROL_PLAY_PAUSE
// CONSUMER_CONTROL_STOP
// CONSUMER_CONTROL_SCAN_NEXT
// CONSUMER_CONTROL_SCAN_PREVIOUS
// CONSUMER_CONTROL_BRIGHTNESS_INCREMENT
// CONSUMER_CONTROL_BRIGHTNESS_DECREMENT
// CONSUMER_CONTROL_POWER
// CONSUMER_CONTROL_HOME
// CONSUMER_CONTROL_BACK
// CONSUMER_CONTROL_FORWARD
// CONSUMER_CONTROL_REFRESH
//
// =============================================
#ifndef ARDUINO_USB_MODE
#error This ESP32 SoC has no Native USB interface
#elif ARDUINO_USB_MODE == 1
#error USB Mode must be set to "USB-OTG (TinyUSB)" in Tools menu
#endif
#include "USB.h"
#include "USBHIDKeyboard.h"
USBHIDKeyboard Keyboard;
// Delay between each character when typing strings (ms)
// Samsung TV is slow — 150ms per char prevents garbled input
const int CHAR_DELAY = 250;
// Delay between menu navigation steps (ms)
const int NAV_DELAY = 2000;
void slowType(const char* text) {
while (*text) {
Keyboard.write(*text);
delay(CHAR_DELAY);
text++;
}
}
void pressKey(uint8_t key) {
Keyboard.press(key);
delay(50);
Keyboard.releaseAll();
}
void setup() {
Keyboard.begin();
USB.begin();
// Wait for USB HID enumeration
delay(3000);
// Navigate past initial screens
pressKey(KEY_RETURN);
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
// Menu navigation - select URL Launcher
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(3000);
// Type Wisar Digital URL (slow, one char at a time)
slowType("http://app.wisardigital.com/app");
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
// Continue navigation
pressKey(KEY_RIGHT_ARROW);
delay(NAV_DELAY);
// Navigate sub-menu
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_RIGHT_ARROW);
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
// Navigate to PIN entry
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_DOWN_ARROW);
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
// Enter PIN (000000000000)
slowType("000000000000");
delay(NAV_DELAY);
pressKey(KEY_RETURN);
delay(NAV_DELAY);
// Done
Keyboard.end();
}
void loop() {
// One-shot payload — nothing to do
}
+16
View File
@@ -0,0 +1,16 @@
# ScoutIT Remote Support (RustDesk) - Minimal Install
# Downloads the MSI and runs msiexec /qn. Nothing else.
$url = 'https://files.scoutitsystems.com/public/api/raw?file=%2F&hash=WFvdb01vISpdzEic7NWFeA'
$msi = "$env:TEMP\pinnacle-scoutit-remotesupport.msi"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Write-Host "Downloading MSI..."
(New-Object System.Net.WebClient).DownloadFile($url, $msi)
Write-Host "Installing silently..."
Start-Process -FilePath msiexec.exe -ArgumentList "/i `"$msi`" /qn" -Wait
Remove-Item $msi -Force -ErrorAction SilentlyContinue
Write-Host "Done."
+16
View File
@@ -0,0 +1,16 @@
# ScoutIT Remote Support (RustDesk) - Minimal Install
# Downloads the MSI and runs msiexec /qn. Nothing else.
$url = 'https://files.scoutitsystems.com/public/api/raw?file=%2F&hash=CUDWa6I1NH9rgZKsdv1xGg'
$msi = "$env:TEMP\tadco-scoutit-remotesupport.msi"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Write-Host "Downloading MSI..."
(New-Object System.Net.WebClient).DownloadFile($url, $msi)
Write-Host "Installing silently..."
Start-Process -FilePath msiexec.exe -ArgumentList "/i `"$msi`" /qn" -Wait
Remove-Item $msi -Force -ErrorAction SilentlyContinue
Write-Host "Done."
+821
View File
@@ -0,0 +1,821 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Build a WinPE x64 image for netboot.xyz (SMB/TFTP delivery)
.DESCRIPTION
Builds a full-featured WinPE x64 image and automatically downloads +
injects the four NIC driver packs that cover ~95% of real hardware:
Intel - All Intel i210 / i219 / i225 / X550 / X710 adapters
Realtek - Onboard GbE and 2.5GbE (RTL8111 / RTL8125 / RTL8168)
Broadcom - Dell/HP server NICs and enterprise workstations
VirtIO - QEMU / KVM / Proxmox VMs (NetKVM, vioscsi, viostor)
Download sources:
Intel - Intel download CDN (page-scraped for current version)
Realtek - Microsoft Windows Update Catalog
Broadcom - Microsoft Windows Update Catalog
VirtIO - GitHub releases API (virtio-win/virtio-win)
All downloads are non-fatal. If one pack fails, the build continues
with whatever succeeded. Failed packs are listed in the summary.
All paths default to G:\
.REQUIREMENTS
- Windows 10/11 or Windows Server 2016+
- Windows ADK https://go.microsoft.com/fwlink/?linkid=2271336
- WinPE add-on https://go.microsoft.com/fwlink/?linkid=2271337
- Run as Administrator
- G:\ must be present with at least 8 GB free (driver packs add ~1.5 GB)
- Internet access to download NIC driver packs
.PARAMETER WorkDir
Temporary build workspace (wiped each run). Default: G:\WinPE_Build
.PARAMETER OutputDir
Final TFTP output directory. Default: G:\WinPE_Output
.PARAMETER ExtraDriversDir
Optional folder of additional .inf drivers to inject on top of the
downloaded packs. Default: G:\WinPE_Drivers (created if missing)
.PARAMETER SkipNICDownload
Skip all NIC pack downloads. Use if you already have packs in ExtraDriversDir
or want to build without internet access.
.PARAMETER Lang
Language code for optional-component language packs. Default: en-us
.PARAMETER ADKRoot
ADK install path override.
.EXAMPLE
.\Build-WinPE.ps1
.EXAMPLE
.\Build-WinPE.ps1 -SkipNICDownload -ExtraDriversDir "G:\MyDrivers"
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$WorkDir = 'G:\WinPE_Build',
[string]$OutputDir = 'G:\WinPE_Output',
[string]$ExtraDriversDir = 'G:\WinPE_Drivers',
[switch]$SkipNICDownload,
[string]$Lang = 'en-us',
[string]$ADKRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# ── Console helpers ────────────────────────────────────────────────────────────
function Write-Step { param($m) Write-Host "`n==► $m" -ForegroundColor Cyan }
function Write-Ok { param($m) Write-Host " OK $m" -ForegroundColor Green }
function Write-Warn { param($m) Write-Host " !! $m" -ForegroundColor Yellow }
function Write-Info { param($m) Write-Host " .. $m" -ForegroundColor DarkCyan }
function Write-Fail { param($m) Write-Host "`n !! $m`n" -ForegroundColor Red; exit 1 }
# ── Derived paths ──────────────────────────────────────────────────────────────
$WinPERoot = Join-Path $ADKRoot 'Windows Preinstallation Environment'
$CopyPeCmd = Join-Path $WinPERoot 'copype.cmd'
$OcRoot = Join-Path $WinPERoot 'amd64\WinPE_OCs'
$NICPacksDir = Join-Path $WorkDir 'NICPacks' # downloaded + extracted packs land here
$WimPath = "$WorkDir\media\sources\boot.wim"
$MountPath = "$WorkDir\mount"
# Track which packs succeeded/failed for the final summary
$NICPackStatus = [ordered]@{
Intel = 'Pending'
Realtek = 'Pending'
Broadcom = 'Pending'
VirtIO = 'Pending'
}
# ==============================================================================
# HELPER: Windows Update Catalog driver downloader
#
# The WU Catalog is the most reliable source for Intel / Realtek / Broadcom
# because it is maintained by Microsoft, requires no account, and returns
# genuine signed driver CABs directly from the Windows Update CDN.
#
# Flow:
# 1. Search the catalog with a query string
# 2. Extract update GUIDs from the results HTML
# 3. POST each GUID to DownloadDialog.aspx to get the direct CAB URL
# 4. Download the CAB and extract it with Windows' built-in expand.exe
# ==============================================================================
function Get-WUCatalogDrivers {
param(
[string]$SearchQuery, # query to run on the catalog
[string]$DestDir, # folder to extract the CAB into
[string]$Label # friendly name for log output
)
Write-Info "[$Label] Searching Windows Update Catalog..."
$searchUri = 'https://www.catalog.update.microsoft.com/Search.aspx?q=' +
[Uri]::EscapeUriString($SearchQuery)
$session = $null
$searchHtml = $null
try {
$searchHtml = (Invoke-WebRequest -Uri $searchUri -UseBasicParsing `
-SessionVariable session -TimeoutSec 60 -ErrorAction Stop).Content
} catch {
Write-Warn "[$Label] Catalog search failed: $($_.Exception.Message)"
return $null
}
# Extract update GUIDs from goToDetails('guid') onclick handlers
$guids = [regex]::Matches($searchHtml,
"goToDetails\('([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})'\)") |
ForEach-Object { $_.Groups[1].Value } |
Select-Object -Unique -First 8
if ($guids.Count -eq 0) {
Write-Warn "[$Label] No catalog entries found for: $SearchQuery"
return $null
}
Write-Info "[$Label] Found $($guids.Count) entries, locating driver CAB..."
$postBase = "&updateIDsBlockedForImport=&wsusApiPresent=&contentImport=&sku=&serverName=&ssl=&portNumber=&version="
foreach ($guid in $guids) {
try {
$postBody = "updateIDs=[{`"size`":0,`"languages`":`"`",`"uidInfo`":`"$guid`",`"updateID`":`"$guid`"}]$postBase"
$dlHtml = (Invoke-WebRequest -Uri 'https://www.catalog.update.microsoft.com/DownloadDialog.aspx' `
-Method POST -Body $postBody -WebSession $session `
-UseBasicParsing -TimeoutSec 30 -ErrorAction Stop).Content
# Download URLs appear as string literals in the response JS
$cabUrl = [regex]::Match($dlHtml, "https://[^'`"\s]+\.cab").Value
if (-not $cabUrl) { continue }
Write-Info "[$Label] Downloading CAB from Windows Update CDN..."
$cabFile = Join-Path $NICPacksDir "$Label.cab"
Invoke-WebRequest -Uri $cabUrl -OutFile $cabFile -UseBasicParsing `
-TimeoutSec 300 -ErrorAction Stop
# Extract CAB using Windows' built-in expand.exe
New-Item -ItemType Directory -Path $DestDir -Force | Out-Null
$expandOut = & expand.exe -F:* $cabFile $DestDir 2>&1
if (-not (Get-ChildItem $DestDir -Recurse -Filter '*.inf' -ErrorAction SilentlyContinue)) {
Write-Warn "[$Label] CAB extracted but contains no INF files — trying next entry..."
Remove-Item $DestDir -Recurse -Force -ErrorAction SilentlyContinue
continue
}
$infCount = (Get-ChildItem $DestDir -Recurse -Filter '*.inf').Count
Write-Ok "[$Label] $infCount INF file(s) extracted"
return $DestDir
} catch {
# Non-fatal — try the next GUID
continue
}
}
Write-Warn "[$Label] Could not find a usable driver CAB in any catalog entry."
return $null
}
# ==============================================================================
# DRIVER PACK: Intel
#
# Intel publishes their NIC drivers (PROWinx64) on their download mirror.
# We scrape the product page to get the current version's download URL so the
# script always fetches the latest without hardcoded version numbers.
# Falls back to Windows Update Catalog if the Intel page scrape fails.
# ==============================================================================
function Get-IntelNICDrivers {
param([string]$DestDir)
Write-Step 'Downloading Intel NIC driver pack'
$intelDest = Join-Path $NICPacksDir 'Intel_raw'
$intelExtract = Join-Path $NICPacksDir 'Intel'
# ── Attempt 1: scrape Intel download page for current ZIP URL ─────────────
try {
Write-Info '[Intel] Fetching current version from Intel download center...'
$page = (Invoke-WebRequest -Uri 'https://www.intel.com/content/www/us/en/download/15084/' `
-UseBasicParsing -TimeoutSec 30 -ErrorAction Stop).Content
# Intel embeds download links in the page HTML
# Try ZIP first (driver-only, no installer overhead)
$zipUrl = [regex]::Match($page,
'https://downloadmirror\.intel\.com/\d+/PROWinx64\.zip').Value
# Fall back to EXE if no ZIP found
$exeUrl = [regex]::Match($page,
'https://downloadmirror\.intel\.com/\d+/PROWinx64\.exe').Value
$downloadUrl = if ($zipUrl) { $zipUrl } elseif ($exeUrl) { $exeUrl } else { $null }
if ($downloadUrl) {
$ext = if ($zipUrl) { 'zip' } else { 'exe' }
$rawFile = Join-Path $NICPacksDir "PROWinx64.$ext"
$version = [regex]::Match($downloadUrl, 'downloadmirror\.intel\.com/(\d+)/').Groups[1].Value
Write-Info "[Intel] Version ID: $version — downloading $ext ($([math]::Round((Invoke-WebRequest $downloadUrl -Method Head -UseBasicParsing).Headers.'Content-Length'/1MB)) MB)..."
Invoke-WebRequest -Uri $downloadUrl -OutFile $rawFile -UseBasicParsing -TimeoutSec 600
New-Item -ItemType Directory -Path $intelExtract -Force | Out-Null
if ($ext -eq 'zip') {
Expand-Archive -Path $rawFile -DestinationPath $intelExtract -Force
} else {
# NSIS self-extracting EXE — try silent extract flag
$proc = Start-Process -FilePath $rawFile `
-ArgumentList "/s /f `"$intelExtract`"" -Wait -PassThru
if ($proc.ExitCode -ne 0 -or -not (Get-ChildItem $intelExtract -ErrorAction SilentlyContinue)) {
# Some Intel packages use a different flag
Start-Process -FilePath $rawFile `
-ArgumentList "-extract:`"$intelExtract`"" -Wait
}
}
# Intel puts WinPE-compatible drivers in the NDIS subfolder
$ndisPath = Get-ChildItem $intelExtract -Recurse -Filter 'NDIS' |
Where-Object { $_.PSIsContainer } | Select-Object -First 1
if ($ndisPath) {
Write-Ok "[Intel] NDIS folder found: $($ndisPath.FullName)"
$NICPackStatus['Intel'] = 'OK (Intel CDN)'
return $ndisPath.FullName
} else {
# NDIS folder not found — inject everything extracted
Write-Warn '[Intel] NDIS subfolder not found — will inject full extracted package.'
$NICPackStatus['Intel'] = 'OK (Intel CDN, full package)'
return $intelExtract
}
}
} catch {
Write-Warn "[Intel] Intel CDN attempt failed: $($_.Exception.Message)"
}
# ── Attempt 2: Windows Update Catalog ─────────────────────────────────────
Write-Info '[Intel] Falling back to Windows Update Catalog...'
$result = Get-WUCatalogDrivers `
-SearchQuery 'Intel Network Adapter Driver Windows 10 x64' `
-DestDir $intelExtract `
-Label 'Intel'
if ($result) {
$NICPackStatus['Intel'] = 'OK (WU Catalog)'
return $result
}
$NICPackStatus['Intel'] = 'FAILED'
return $null
}
# ==============================================================================
# DRIVER PACK: Realtek
#
# Realtek's download site requires JavaScript / CAPTCHA, so we go straight to
# the Windows Update Catalog which carries the identical signed drivers.
# Targets: RTL8111 / RTL8168 / RTL8125 (2.5G) / RTL8153 (USB) family.
# ==============================================================================
function Get-RealtekNICDrivers {
Write-Step 'Downloading Realtek NIC driver pack'
$realtekDest = Join-Path $NICPacksDir 'Realtek'
$result = Get-WUCatalogDrivers `
-SearchQuery 'Realtek PCIe GbE Family Controller Windows 10 x64' `
-DestDir $realtekDest `
-Label 'Realtek'
if ($result) {
$NICPackStatus['Realtek'] = 'OK (WU Catalog)'
return $result
}
# Alternate search if primary returns nothing useful
$result = Get-WUCatalogDrivers `
-SearchQuery 'Realtek USB FE Family Controller Windows 10 x64' `
-DestDir $realtekDest `
-Label 'Realtek-USB'
if ($result) {
$NICPackStatus['Realtek'] = 'OK (WU Catalog, USB variant)'
return $result
}
$NICPackStatus['Realtek'] = 'FAILED'
return $null
}
# ==============================================================================
# DRIVER PACK: Broadcom
#
# Covers Broadcom NetXtreme-I/II (BCM5720 / BCM5721 / BCM57XX) found in
# Dell PowerEdge servers, HP ProLiant, and many enterprise workstations.
# ==============================================================================
function Get-BroadcomNICDrivers {
Write-Step 'Downloading Broadcom NIC driver pack'
$broadcomDest = Join-Path $NICPacksDir 'Broadcom'
$result = Get-WUCatalogDrivers `
-SearchQuery 'Broadcom NetXtreme Gigabit Ethernet Windows 10 x64' `
-DestDir $broadcomDest `
-Label 'Broadcom'
if ($result) {
$NICPackStatus['Broadcom'] = 'OK (WU Catalog)'
return $result
}
$result = Get-WUCatalogDrivers `
-SearchQuery 'Broadcom NetXtreme-I Netlink Windows 10 x64' `
-DestDir $broadcomDest `
-Label 'Broadcom-II'
if ($result) {
$NICPackStatus['Broadcom'] = 'OK (WU Catalog, NetXtreme-I)'
return $result
}
$NICPackStatus['Broadcom'] = 'FAILED'
return $null
}
# ==============================================================================
# DRIVER PACK: VirtIO
#
# Fedora's virtio-win project maintains signed VirtIO drivers for Windows.
# We pull the latest ISO from GitHub releases and extract only the folders
# relevant to WinPE networking and storage:
#
# NetKVM - VirtIO NIC (QEMU/KVM/Proxmox)
# vioscsi - VirtIO SCSI controller
# viostor - VirtIO block storage
# vioserial - VirtIO serial (for some boot environments)
#
# The ISO is mounted using Windows' built-in Mount-DiskImage — no 7-zip needed.
# ==============================================================================
function Get-VirtIODrivers {
Write-Step 'Downloading VirtIO driver pack'
$virtioExtract = Join-Path $NICPacksDir 'VirtIO'
$isoPath = Join-Path $NICPacksDir 'virtio-win.iso'
try {
Write-Info '[VirtIO] Querying GitHub for latest virtio-win release...'
$release = Invoke-RestMethod `
-Uri 'https://api.github.com/repos/virtio-win/virtio-win/releases/latest' `
-Headers @{ 'User-Agent' = 'WinPE-Builder' } `
-TimeoutSec 30 -ErrorAction Stop
# Find the standalone ISO asset (not the RPM or virtio-win-gt ISO)
$isoAsset = $release.assets |
Where-Object { $_.name -match '^virtio-win-[\d\.]+\.iso$' } |
Select-Object -First 1
if (-not $isoAsset) {
Write-Warn '[VirtIO] Could not locate ISO asset in latest release.'
$NICPackStatus['VirtIO'] = 'FAILED'
return $null
}
$sizeMB = [math]::Round($isoAsset.size / 1MB)
Write-Info "[VirtIO] Downloading $($isoAsset.name) ($sizeMB MB)..."
Invoke-WebRequest -Uri $isoAsset.browser_download_url -OutFile $isoPath `
-UseBasicParsing -TimeoutSec 600 -ErrorAction Stop
Write-Info '[VirtIO] Mounting ISO...'
$mountResult = Mount-DiskImage -ImagePath $isoPath -PassThru -ErrorAction Stop
$driveLetter = ($mountResult | Get-Volume).DriveLetter
if (-not $driveLetter) {
Write-Warn '[VirtIO] ISO mounted but drive letter not assigned.'
Dismount-DiskImage -ImagePath $isoPath | Out-Null
$NICPackStatus['VirtIO'] = 'FAILED'
return $null
}
New-Item -ItemType Directory -Path $virtioExtract -Force | Out-Null
# Driver subdirectories relevant to WinPE
# w10\amd64 works for both Windows 10 and 11 WinPE
$driverMap = [ordered]@{
'NetKVM\w10\amd64' = 'NetKVM' # VirtIO NIC
'vioscsi\w10\amd64' = 'vioscsi' # VirtIO SCSI
'viostor\w10\amd64' = 'viostor' # VirtIO block storage
'vioserial\w10\amd64' = 'vioserial' # VirtIO serial
}
$copied = 0
foreach ($src in $driverMap.Keys) {
$srcPath = Join-Path "${driveLetter}:\" $src
$destPath = Join-Path $virtioExtract $driverMap[$src]
if (Test-Path $srcPath) {
Copy-Item $srcPath $destPath -Recurse -Force
Write-Ok " [VirtIO] Copied: $src"
$copied++
} else {
Write-Warn " [VirtIO] Not found in ISO: $src"
}
}
Dismount-DiskImage -ImagePath $isoPath | Out-Null
Write-Ok "[VirtIO] $copied driver folder(s) extracted"
if ($copied -gt 0) {
$NICPackStatus['VirtIO'] = "OK (GitHub — $($release.tag_name))"
return $virtioExtract
} else {
$NICPackStatus['VirtIO'] = 'FAILED (no folders extracted from ISO)'
return $null
}
} catch {
# If ISO is still mounted, clean up
if (Test-Path $isoPath) {
Dismount-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue | Out-Null
}
Write-Warn "[VirtIO] Failed: $($_.Exception.Message)"
$NICPackStatus['VirtIO'] = 'FAILED'
return $null
}
}
# ==============================================================================
# STEP 1 — Prerequisites
# ==============================================================================
Write-Step 'Checking prerequisites'
if (-not (Test-Path 'G:\')) {
Write-Fail 'G:\ is not available. Attach or map the drive before running.'
}
Write-Ok 'G:\ is present'
if (-not (Test-Path $WinPERoot)) {
Write-Fail @"
WinPE add-on not found at: $WinPERoot
Install in order:
ADK https://go.microsoft.com/fwlink/?linkid=2271336
WinPE add-on https://go.microsoft.com/fwlink/?linkid=2271337
"@
}
if (-not (Test-Path $CopyPeCmd)) {
Write-Fail "copype.cmd not found: $CopyPeCmd — reinstall the WinPE add-on."
}
if (-not (Test-Path $OcRoot)) {
Write-Fail "WinPE optional components not found: $OcRoot"
}
$gFreeGB = [math]::Round((Get-PSDrive G).Free / 1GB, 1)
if ($gFreeGB -lt 5) {
Write-Warn "G:\ has only ${gFreeGB} GB free — 8 GB recommended with NIC packs."
} else {
Write-Ok "G:\ free space : ${gFreeGB} GB"
}
Write-Ok "ADK root : $ADKRoot"
Write-Ok "WinPE add-on : $WinPERoot"
# ==============================================================================
# STEP 2 — Directories
# ==============================================================================
Write-Step 'Preparing directories on G:\'
if (Test-Path $WorkDir) {
Write-Warn "Cleaning work directory: $WorkDir"
Get-WindowsImage -Mounted -ErrorAction SilentlyContinue |
Where-Object { $_.Path -like "$WorkDir*" } |
ForEach-Object { Dismount-WindowsImage -Path $_.Path -Discard | Out-Null }
Remove-Item $WorkDir -Recurse -Force
}
@($WorkDir, $OutputDir, $ExtraDriversDir, $NICPacksDir) |
ForEach-Object { New-Item -ItemType Directory -Path $_ -Force | Out-Null }
Write-Ok "WorkDir : $WorkDir"
Write-Ok "OutputDir : $OutputDir"
Write-Ok "ExtraDriversDir: $ExtraDriversDir"
Write-Ok "NICPacksDir : $NICPacksDir"
# ==============================================================================
# STEP 3 — Download NIC driver packs
# ==============================================================================
$injectionPaths = [System.Collections.Generic.List[string]]::new()
if ($SkipNICDownload) {
Write-Step 'Skipping NIC pack downloads (-SkipNICDownload)'
$NICPackStatus['Intel'] = $NICPackStatus['Realtek'] = `
$NICPackStatus['Broadcom'] = $NICPackStatus['VirtIO'] = 'Skipped'
} else {
Write-Host "`n Downloading NIC driver packs — failures are non-fatal." -ForegroundColor DarkCyan
Write-Host " Internet access required. This may take several minutes." -ForegroundColor DarkCyan
$intelPath = Get-IntelNICDrivers
$realtekPath = Get-RealtekNICDrivers
$broadcomPath = Get-BroadcomNICDrivers
$virtioPath = Get-VirtIODrivers
foreach ($p in @($intelPath, $realtekPath, $broadcomPath, $virtioPath)) {
if ($p) { $injectionPaths.Add($p) }
}
Write-Step 'NIC pack download results'
foreach ($k in $NICPackStatus.Keys) {
$status = $NICPackStatus[$k]
if ($status -like 'OK*') { Write-Ok "$k : $status" }
elseif ($status -eq 'FAILED'){ Write-Warn "$k : FAILED (see warnings above)" }
else { Write-Info "$k : $status" }
}
}
# Add any extra drivers from ExtraDriversDir
$extraCount = (Get-ChildItem $ExtraDriversDir -Recurse -Filter '*.inf' -ErrorAction SilentlyContinue).Count
if ($extraCount -gt 0) {
Write-Info "ExtraDriversDir: $extraCount additional INF(s) will be injected"
$injectionPaths.Add($ExtraDriversDir)
}
# ==============================================================================
# STEP 4 — copype
# ==============================================================================
Write-Step 'Running copype amd64 — creating WinPE base on G:\'
$cpResult = & cmd.exe /c "`"$CopyPeCmd`" amd64 `"$WorkDir`"" 2>&1
if ($LASTEXITCODE -ne 0) { Write-Fail "copype.cmd failed (exit $LASTEXITCODE):`n$cpResult" }
Write-Ok 'WinPE base structure created'
# ==============================================================================
# STEP 5 — Mount boot.wim
# ==============================================================================
if (-not (Test-Path $WimPath)) { Write-Fail "boot.wim not found: $WimPath" }
New-Item -ItemType Directory -Path $MountPath -Force | Out-Null
Write-Step 'Mounting boot.wim'
Mount-WindowsImage -ImagePath $WimPath -Index 1 -Path $MountPath | Out-Null
Write-Ok "Mounted: $MountPath"
# ==============================================================================
# STEP 6 — Optional packages
# ==============================================================================
function Add-WinPEPackage {
param([string]$Name)
$pkg = Join-Path $OcRoot "$Name.cab"
$langPkg = Join-Path $OcRoot "$Lang\${Name}_${Lang}.cab"
if (-not (Test-Path $pkg)) { Write-Warn "Package not found, skipping: $Name"; return }
try {
Add-WindowsPackage -Path $MountPath -PackagePath $pkg -WarningAction SilentlyContinue | Out-Null
Write-Ok " $Name"
} catch { Write-Warn " $Name — $($_.Exception.Message)" }
if (Test-Path $langPkg) {
try {
Add-WindowsPackage -Path $MountPath -PackagePath $langPkg -WarningAction SilentlyContinue | Out-Null
Write-Ok " $Name [$Lang]"
} catch { Write-Warn " $Name [$Lang] language pack — non-fatal" }
}
}
Write-Step 'Installing optional WinPE packages'
# Core — install dependencies first
Add-WinPEPackage 'WinPE-WMI'
Add-WinPEPackage 'WinPE-NetFX'
Add-WinPEPackage 'WinPE-Scripting'
Add-WinPEPackage 'WinPE-PowerShell' # requires WMI + NetFX + Scripting
# Deployment
Add-WinPEPackage 'WinPE-DismCmdlets' # requires PowerShell
Add-WinPEPackage 'WinPE-StorageWMI' # requires WMI
Add-WinPEPackage 'WinPE-HTA'
Add-WinPEPackage 'WinPE-SecureStartup'
Add-WinPEPackage 'WinPE-WDS-Tools' # skipped gracefully if absent on this ADK
# Disk imaging / recovery
Add-WinPEPackage 'WinPE-EnhancedStorage'
Add-WinPEPackage 'WinPE-FMAPI'
# Diagnostics / networking
Add-WinPEPackage 'WinPE-Dot3Svc'
Add-WinPEPackage 'WinPE-RNDIS'
Write-Ok 'All packages processed'
# ==============================================================================
# STEP 7 — Inject NIC drivers + extra drivers
# ==============================================================================
Write-Step 'Injecting NIC driver packs into WIM'
if ($injectionPaths.Count -eq 0) {
Write-Warn 'No driver paths to inject — WIM will have inbox drivers only.'
} else {
foreach ($driverPath in $injectionPaths) {
$infCount = (Get-ChildItem $driverPath -Recurse -Filter '*.inf' -ErrorAction SilentlyContinue).Count
if ($infCount -eq 0) {
Write-Warn "No INF files found in $driverPath — skipping."
continue
}
Write-Info "Injecting $infCount INF(s) from: $driverPath"
try {
Add-WindowsDriver -Path $MountPath -Driver $driverPath `
-Recurse -ForceUnsigned -ErrorAction Stop | Out-Null
Write-Ok " Injected: $(Split-Path $driverPath -Leaf) ($infCount INFs)"
} catch {
Write-Warn " Injection warning for $driverPath`: $($_.Exception.Message)"
}
}
}
# ==============================================================================
# STEP 8 — startnet.cmd
# ==============================================================================
Write-Step 'Writing startnet.cmd'
@'
@echo off
:: ===========================================================================
:: WinPE startup — edit the SMB block below for your environment
:: ===========================================================================
echo.
echo [WinPE] Initializing hardware and network...
wpeinit
:: Wait for NIC link-up
ping -n 5 127.0.0.1 > nul
echo [WinPE] Network status:
ipconfig
:: ---------------------------------------------------------------------------
:: Map deployment SMB share — uncomment ONE option
::
:: A) Anonymous share
:: net use Z: \\192.168.1.10\DeploymentShare$
::
:: B) Static domain credentials
:: net use Z: \\192.168.1.10\DeploymentShare$ /user:DOMAIN\svcDeploy P@ssw0rd
::
:: C) Prompt for password
:: net use Z: \\192.168.1.10\DeploymentShare$ /user:DOMAIN\svcDeploy *
:: ---------------------------------------------------------------------------
:: net use Z: \\192.168.1.10\DeploymentShare$
:: if errorlevel 1 ( echo [ERROR] Could not map share & pause & goto :shell )
:: -- MDT LiteTouch ----------------------------------------------------------
:: echo [WinPE] Launching LiteTouch...
:: Z:\Scripts\LiteTouch.vbs
:: -- Custom PowerShell script -----------------------------------------------
:: powershell.exe -ExecutionPolicy Bypass -File Z:\Scripts\Deploy.ps1
:shell
echo.
echo [WinPE] Ready. Mapped drives:
net use
echo.
echo Type EXIT to reboot.
cmd.exe /k
'@ | Set-Content -Path "$MountPath\Windows\System32\startnet.cmd" -Encoding ASCII
Write-Ok 'startnet.cmd written'
# ==============================================================================
# STEP 9 — Scratch space
# ==============================================================================
Write-Step 'Setting scratch space to 512 MB'
Set-WindowsImage -Path $MountPath -ScratchSpaceSize 512 | Out-Null
Write-Ok 'Scratch space: 512 MB'
# ==============================================================================
# STEP 10 — Cleanup inside WIM
# ==============================================================================
Write-Step 'Removing logs and temp files from WIM'
@("$MountPath\Windows\Logs\*", "$MountPath\Windows\Temp\*", "$MountPath\Windows\CBS\*") |
ForEach-Object { Remove-Item $_ -Recurse -Force -ErrorAction SilentlyContinue }
Write-Ok 'WIM cleaned'
# ==============================================================================
# STEP 11 — Unmount and commit
# ==============================================================================
Write-Step 'Unmounting WIM and committing'
Dismount-WindowsImage -Path $MountPath -Save | Out-Null
Write-Ok 'WIM committed'
# ==============================================================================
# STEP 12 — Export optimized WIM
# ==============================================================================
Write-Step 'Exporting WIM with maximum compression'
$optimized = "$WorkDir\boot_opt.wim"
Export-WindowsImage -SourceImagePath $WimPath -SourceIndex 1 `
-DestinationImagePath $optimized -CompressionType Maximum | Out-Null
Remove-Item $WimPath -Force
Move-Item $optimized $WimPath
Write-Ok 'WIM optimized'
# ==============================================================================
# STEP 13 — Build TFTP output tree
# ==============================================================================
Write-Step 'Building TFTP output tree on G:\'
$TftpRoot = Join-Path $OutputDir 'tftp'
@("$TftpRoot\Boot", "$TftpRoot\EFI\Boot", "$TftpRoot\EFI\Microsoft\Boot") |
ForEach-Object { New-Item -ItemType Directory -Path $_ -Force | Out-Null }
Copy-Item "$WorkDir\media\bootmgr" "$TftpRoot\bootmgr" -Force
Copy-Item "$WorkDir\media\Boot\BCD" "$TftpRoot\Boot\BCD" -Force
Copy-Item "$WorkDir\media\Boot\boot.sdi" "$TftpRoot\Boot\boot.sdi" -Force
Copy-Item "$WorkDir\media\sources\boot.wim" "$TftpRoot\Boot\boot.wim" -Force
@{
"$WorkDir\media\bootmgr.efi" = "$TftpRoot\bootmgr.efi"
"$WorkDir\media\EFI\Boot\bootx64.efi" = "$TftpRoot\EFI\Boot\bootx64.efi"
"$WorkDir\media\EFI\Microsoft\Boot\BCD" = "$TftpRoot\EFI\Microsoft\Boot\BCD"
}.GetEnumerator() | ForEach-Object {
if (Test-Path $_.Key) { Copy-Item $_.Key $_.Value -Force; Write-Ok " $(Split-Path $_.Key -Leaf)" }
else { Write-Warn " Not found (non-fatal): $(Split-Path $_.Key -Leaf)" }
}
Write-Ok "TFTP tree ready: $TftpRoot"
# ==============================================================================
# STEP 14 — Summary
# ==============================================================================
$wimSizeMB = [math]::Round((Get-Item "$TftpRoot\Boot\boot.wim").Length / 1MB, 0)
Write-Host @"
============================================================
WinPE x64 Build Complete
============================================================
TFTP output : $TftpRoot
boot.wim : ${wimSizeMB} MB
NIC driver pack results:
"@ -ForegroundColor Cyan
foreach ($k in $NICPackStatus.Keys) {
$status = $NICPackStatus[$k]
if ($status -like 'OK*') { Write-Host " OK $k : $status" -ForegroundColor Green }
elseif ($status -eq 'FAILED') { Write-Host " !! $k : FAILED" -ForegroundColor Yellow }
else { Write-Host " -- $k : $status" -ForegroundColor DarkCyan }
}
Write-Host @"
If any pack shows FAILED:
- Check internet connectivity and re-run
- Or download manually and place INF/SYS/CAT files in G:\WinPE_Drivers,
then re-run with -SkipNICDownload to skip re-downloading
Place wimboot in the TFTP root before booting:
https://github.com/ipxe/wimboot/releases/latest/download/wimboot
TFTP layout:
tftp\
├── wimboot
├── bootmgr / bootmgr.efi
├── Boot\ BCD boot.sdi boot.wim (${wimSizeMB} MB)
└── EFI\ Boot\bootx64.efi Microsoft\Boot\BCD
To update startnet.cmd without a full rebuild:
Mount-WindowsImage -ImagePath '$TftpRoot\Boot\boot.wim' -Index 1 -Path G:\WinPE_Mount
notepad G:\WinPE_Mount\Windows\System32\startnet.cmd
Dismount-WindowsImage -Path G:\WinPE_Mount -Save
"@ -ForegroundColor Cyan