mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0dc5a6ae9 | |||
| 6b548a34e6 | |||
| 8c5629c9ce | |||
| e7589f6ceb | |||
| 711582ae71 | |||
| db22c1801d | |||
| 422bc33da7 | |||
| 96603f025a | |||
| a8fecd133e | |||
| 7f10811c05 | |||
| f09c98906a | |||
| 04dfb5f327 | |||
| dc801e9cc9 | |||
| a8e2ab941f | |||
| c83bc8c769 | |||
| 0eb7f66c2b | |||
| d70615a32d | |||
| 26694f30e3 | |||
| 3563639ce0 | |||
| a42f49e1fa | |||
| 42b0b0c350 | |||
| 6e6abfe833 | |||
| baa696b880 | |||
| 27eebeb9cb | |||
| d349e5e4fb | |||
| 53a47511d8 | |||
| 518b4d4e62 | |||
| a771136761 | |||
| 36ee6f64bc | |||
| 2257b72255 | |||
| a65c9b5a18 | |||
| d38e461246 | |||
| edc9901e7e | |||
| 48b55df18e | |||
| cf9c605c34 | |||
| d6e7fd314f | |||
| a193c283f3 | |||
| 477d51fbbb | |||
| 3e3492bbab | |||
| 97e1ec2be4 | |||
| 6e95ff92b1 | |||
| d4274d54d2 | |||
| aee33a6a4b | |||
| 25a0928195 |
@@ -1,5 +1,67 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
# 2603.1
|
||||||
|
|
||||||
|
## What's Changed
|
||||||
|
|
||||||
|
### UI Out of Preview
|
||||||
|
|
||||||
|
The UI is finally out of preview and the code from the UI branch has been pushed to main.
|
||||||
|
|
||||||
|
### Remove old MSU files before servicing
|
||||||
|
|
||||||
|
Fixes an issue where older cumulative update MSU files were mixed with newer files. In Windows 11 24H2, dism treats all MSU files in the same folder as possible update sources, causing servicing issues.
|
||||||
|
|
||||||
|
This will primarily be an issue for those of you who use ISO files that don't have the latest updates, or those of you who create your FFUs with the ESD media shortly after patch Tuesday.
|
||||||
|
|
||||||
|
### Adds OS-scoped update KB folders
|
||||||
|
|
||||||
|
The KB folder where the CU and .NET updates downloads to will now have sub-folders for each Windows version. You may notice that if selecting 25H2, that the KB sub-folder for Windows 11 will show 24H2, this is to keep consistent with Windows 11 LTSC 2024 and Windows Server 2025, which all use the same 24H2 source OS (25H2 is just an eKB, not a full OS Swap Windows release like 24H2 was).
|
||||||
|
|
||||||
|
### .NET Updates stored under their own dedicated KB folder
|
||||||
|
|
||||||
|
This is more so to keep things clean, rather than fixing any sort of technical issue
|
||||||
|
|
||||||
|
### Reworked Windows 10 LTSB\LTSC Cumulative Update Installation
|
||||||
|
|
||||||
|
Since Windows 10 is out of support, offline servicing Windows 10 LTSC builds that are still supported fails due to extended security update requirements. The workaround to this is to install the updates in audit mode. FFU Builder now creates an Apps\LTSCUpdate folder and copies the LCU for Windows 10 LTSC builds still in support to the folder and installs the update in audit mode.
|
||||||
|
|
||||||
|
### Fixes an issue with Update ADK failing for non-English languages
|
||||||
|
|
||||||
|
Fixed a bug where updating the ADK would fail on non-English installations of Windows due to an assumption that the add/remove programs display information would be in English. Update ADK should correctly identify if the latest release of the ADK is installed and work as expected.
|
||||||
|
|
||||||
|
### Added dependency validation when selecting Build USB drive and Copy Drivers
|
||||||
|
|
||||||
|
Fixed an issue where a build would begin even though Copy Drivers to USB was set to true but no USB was inserted. FFU Builder will now check before the build gets started and inform the user if no USB drive is inserted.
|
||||||
|
|
||||||
|
### Normalizes Windows LTSC release versions to handle driver downloads
|
||||||
|
|
||||||
|
Driver downloads would fail if you were building certain LTSC releases due to FFU Builder incorrectly using the LTSC release year instead of the base Windows client version information. When build LTSC FFUs, the drivers should now download as expected.
|
||||||
|
|
||||||
|
### Scopes select-all to visible filtered list items in drivers listview
|
||||||
|
|
||||||
|
When filtering the drivers listview and selecting all driver models using the select all header checkbox, the select all behavior was selecting everything, even the hidden models in the list. If then selecting Download Selected, FFU Builder would download all models, even hidden ones. This now fixes that issue to only select all visible models and download those selected models.
|
||||||
|
|
||||||
|
### Retain downloaded ESD files
|
||||||
|
|
||||||
|
FFU Builder will now allow you to retain a downloaded ESD file. There's a new option on the Build tab to Remove Downloaded ESD File(s) which is checked by default to keep with the previous behavior. The intent here is to prevent from having to re-download the ESD file every time you're doing a build. This gives you another option along with Allow VHDX Caching to reduce the need of redownloading media.
|
||||||
|
|
||||||
|
### Reduce the size of cached VHDX files
|
||||||
|
|
||||||
|
Added some code to reduce the size of the cached VHDX files.
|
||||||
|
|
||||||
|
### Include disk size in VHDX cache validation
|
||||||
|
|
||||||
|
Prevents reusing cached images when the requested disk size changes. Ensures the disk size property is properly saved and verified against existing cache items to maintain configuration accuracy. This makes it so that if you create a new build with a larger disk size and have Allow VHDX caching selected, it won't use a cached VHDX with a smaller size.
|
||||||
|
|
||||||
|
### Enhances file backup and cleanup for cancelled builds
|
||||||
|
|
||||||
|
Improves the current-run cleanup mechanism by tracking file downloads explicitly. This ensures that files downloaded via BITS or preserving older timestamps are correctly identified and removed during cleanup. Extends the run manifest schema to support file backups, allowing for safe restoration of pre-existing scripts and configuration files modified during a run. Additional cleanup logic now correctly prunes residual empty directories after tracked files are removed.
|
||||||
|
|
||||||
|
### Fixed an issue with arm64 ESD downloads
|
||||||
|
|
||||||
|
With the change to how ESD downloads work with 25H2 and the Media Creation Tool, arm64 was broken. This was fixed.
|
||||||
|
|
||||||
# 2602.1 UI Preview
|
# 2602.1 UI Preview
|
||||||
|
|
||||||
## What's Changed
|
## What's Changed
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ Write-Host "---------------------------------------------------" -ForegroundColo
|
|||||||
# Define the path to the scripts
|
# Define the path to the scripts
|
||||||
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
# Define the list of scripts to run, order doesn't matter - if you have a custom script, add it here
|
# Define the list of scripts to run
|
||||||
$scriptList = @(
|
$scriptList = @(
|
||||||
|
"Install-LTSCUpdate.ps1",
|
||||||
"Update-Defender.ps1",
|
"Update-Defender.ps1",
|
||||||
"Install-Office.ps1",
|
"Install-Office.ps1",
|
||||||
"Update-MSRT.ps1",
|
"Update-MSRT.ps1",
|
||||||
|
|||||||
+922
-144
File diff suppressed because it is too large
Load Diff
@@ -837,6 +837,7 @@
|
|||||||
<CheckBox x:Name="chkRemoveFFU" Content="Remove FFU" Margin="5" VerticalAlignment="Center" Tag="Remove FFU after copying to USB drive."/>
|
<CheckBox x:Name="chkRemoveFFU" Content="Remove FFU" Margin="5" VerticalAlignment="Center" Tag="Remove FFU after copying to USB drive."/>
|
||||||
<CheckBox x:Name="chkRemoveApps" Content="Remove Apps Folder Content" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will remove the application content in the Apps folder after the FFU has been captured."/>
|
<CheckBox x:Name="chkRemoveApps" Content="Remove Apps Folder Content" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will remove the application content in the Apps folder after the FFU has been captured."/>
|
||||||
<CheckBox x:Name="chkRemoveUpdates" Content="Remove Downloaded Update Files" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will remove downloaded CU, .NET, MSRT, Defender, Edge, and OneDrive files after being applied/included."/>
|
<CheckBox x:Name="chkRemoveUpdates" Content="Remove Downloaded Update Files" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will remove downloaded CU, .NET, MSRT, Defender, Edge, and OneDrive files after being applied/included."/>
|
||||||
|
<CheckBox x:Name="chkRemoveDownloadedESD" Content="Remove Downloaded ESD file(s)" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will remove downloaded Windows ESD file(s) after they are used."/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ function Invoke-FFUPostBuildCleanup {
|
|||||||
[bool]$RemoveDrivers = $false,
|
[bool]$RemoveDrivers = $false,
|
||||||
[bool]$RemoveFFU = $false,
|
[bool]$RemoveFFU = $false,
|
||||||
[bool]$RemoveApps = $false,
|
[bool]$RemoveApps = $false,
|
||||||
[bool]$RemoveUpdates = $false
|
[bool]$RemoveUpdates = $false,
|
||||||
|
[bool]$RemoveDownloadedESD = $false
|
||||||
)
|
)
|
||||||
$originalProgressPreference = $ProgressPreference
|
$originalProgressPreference = $ProgressPreference
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
try {
|
try {
|
||||||
WriteLog "CommonCleanup: Starting cleanup (CaptureISO=$RemoveCaptureISO DeployISO=$RemoveDeployISO AppsISO=$RemoveAppsISO Drivers=$RemoveDrivers FFU=$RemoveFFU Apps=$RemoveApps Updates=$RemoveUpdates KBPath=$KBPath)."
|
WriteLog "CommonCleanup: Starting cleanup (CaptureISO=$RemoveCaptureISO DeployISO=$RemoveDeployISO AppsISO=$RemoveAppsISO Drivers=$RemoveDrivers FFU=$RemoveFFU Apps=$RemoveApps Updates=$RemoveUpdates RemoveDownloadedESD=$RemoveDownloadedESD KBPath=$KBPath)."
|
||||||
|
|
||||||
# Primary ISO paths (new naming/location)
|
# Primary ISO paths (new naming/location)
|
||||||
if ($RemoveCaptureISO -and -not [string]::IsNullOrWhiteSpace($CaptureISOPath) -and (Test-Path -LiteralPath $CaptureISOPath)) {
|
if ($RemoveCaptureISO -and -not [string]::IsNullOrWhiteSpace($CaptureISOPath) -and (Test-Path -LiteralPath $CaptureISOPath)) {
|
||||||
@@ -95,6 +96,15 @@ function Invoke-FFUPostBuildCleanup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Always remove LTSC update staging folder (out-of-band cleanup exception)
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($AppsPath) -and (Test-Path -LiteralPath $AppsPath -PathType Container)) {
|
||||||
|
$ltscUpdateFolder = Join-Path $AppsPath 'LTSCUpdate'
|
||||||
|
if (Test-Path -LiteralPath $ltscUpdateFolder) {
|
||||||
|
WriteLog "CommonCleanup: Removing LTSC update staging folder $ltscUpdateFolder"
|
||||||
|
try { Remove-Item -LiteralPath $ltscUpdateFolder -Recurse -Force -ErrorAction Stop } catch { WriteLog "CommonCleanup: Failed removing $ltscUpdateFolder : $($_.Exception.Message)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($RemoveUpdates) {
|
if ($RemoveUpdates) {
|
||||||
if (-not [string]::IsNullOrWhiteSpace($AppsPath) -and (Test-Path -LiteralPath $AppsPath)) {
|
if (-not [string]::IsNullOrWhiteSpace($AppsPath) -and (Test-Path -LiteralPath $AppsPath)) {
|
||||||
# Remove per-run app update payloads stored under Apps
|
# Remove per-run app update payloads stored under Apps
|
||||||
@@ -114,6 +124,20 @@ function Invoke-FFUPostBuildCleanup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Remove downloaded ESD files from the root path when requested
|
||||||
|
if ($RemoveDownloadedESD -and -not [string]::IsNullOrWhiteSpace($RootPath) -and (Test-Path -LiteralPath $RootPath -PathType Container)) {
|
||||||
|
WriteLog "CommonCleanup: Removing downloaded ESD files in $RootPath"
|
||||||
|
Get-ChildItem -LiteralPath $RootPath -Filter *.esd -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
try {
|
||||||
|
WriteLog "CommonCleanup: Removing ESD $($_.FullName)"
|
||||||
|
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "CommonCleanup: Failed removing ESD $($_.FullName) : $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WriteLog "CommonCleanup: Completed."
|
WriteLog "CommonCleanup: Completed."
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
@@ -157,6 +157,79 @@ function Invoke-Process {
|
|||||||
return $cmd
|
return $cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-RunManifestPathForDownloadTarget {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Destination
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$currentPath = Split-Path -Path $Destination -Parent
|
||||||
|
if ([string]::IsNullOrWhiteSpace($currentPath)) { return $null }
|
||||||
|
|
||||||
|
while ($currentPath) {
|
||||||
|
$manifestPath = Join-Path -Path $currentPath -ChildPath '.session\currentRun.json'
|
||||||
|
if (Test-Path -LiteralPath $manifestPath -PathType Leaf) {
|
||||||
|
return $manifestPath
|
||||||
|
}
|
||||||
|
|
||||||
|
$parentPath = Split-Path -Path $currentPath -Parent
|
||||||
|
if ([string]::IsNullOrWhiteSpace($parentPath) -or $parentPath -eq $currentPath) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
$currentPath = $parentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Get-RunManifestPathForDownloadTarget failed for '$Destination': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Register-CurrentRunDownloadTarget {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Destination
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Destination)) { return }
|
||||||
|
|
||||||
|
$manifestPath = Get-RunManifestPathForDownloadTarget -Destination $Destination
|
||||||
|
if ([string]::IsNullOrWhiteSpace($manifestPath)) { return }
|
||||||
|
|
||||||
|
$mutexName = 'Global\FFUCurrentRunDownloadTargetsMutex'
|
||||||
|
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$null = $mutex.WaitOne()
|
||||||
|
|
||||||
|
$manifest = Get-Content -LiteralPath $manifestPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
||||||
|
if ($null -eq $manifest) { return }
|
||||||
|
|
||||||
|
if ($null -eq $manifest.PSObject.Properties['DownloadTargets']) {
|
||||||
|
Add-Member -InputObject $manifest -MemberType NoteProperty -Name DownloadTargets -Value @()
|
||||||
|
}
|
||||||
|
|
||||||
|
$downloadTargets = @($manifest.DownloadTargets)
|
||||||
|
if ($Destination -notin $downloadTargets) {
|
||||||
|
$downloadTargets += $Destination
|
||||||
|
$manifest.DownloadTargets = $downloadTargets
|
||||||
|
$manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
|
||||||
|
WriteLog "Registered current-run download target: $Destination"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Register-CurrentRunDownloadTarget failed for '$Destination': $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try { $mutex.ReleaseMutex() | Out-Null } catch {}
|
||||||
|
$mutex.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Function to download a file using BITS with retry and error handling
|
# Function to download a file using BITS with retry and error handling
|
||||||
function Start-BitsTransferWithRetry {
|
function Start-BitsTransferWithRetry {
|
||||||
param (
|
param (
|
||||||
@@ -181,6 +254,10 @@ function Start-BitsTransferWithRetry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Register destination so cancel cleanup can remove this run's downloaded files
|
||||||
|
# even when file timestamps are inherited from the source.
|
||||||
|
Register-CurrentRunDownloadTarget -Destination $Destination
|
||||||
|
|
||||||
$attempt = 0
|
$attempt = 0
|
||||||
$lastError = $null
|
$lastError = $null
|
||||||
$notLoggedOnHResult = [int]0x800704dd
|
$notLoggedOnHResult = [int]0x800704dd
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ function Get-UIConfig {
|
|||||||
RemoveApps = $State.Controls.chkRemoveApps.IsChecked
|
RemoveApps = $State.Controls.chkRemoveApps.IsChecked
|
||||||
RemoveFFU = $State.Controls.chkRemoveFFU.IsChecked
|
RemoveFFU = $State.Controls.chkRemoveFFU.IsChecked
|
||||||
RemoveUpdates = $State.Controls.chkRemoveUpdates.IsChecked
|
RemoveUpdates = $State.Controls.chkRemoveUpdates.IsChecked
|
||||||
|
RemoveDownloadedESD = $State.Controls.chkRemoveDownloadedESD.IsChecked
|
||||||
ShareName = $State.Controls.txtShareName.Text
|
ShareName = $State.Controls.txtShareName.Text
|
||||||
UpdateADK = $State.Controls.chkUpdateADK.IsChecked
|
UpdateADK = $State.Controls.chkUpdateADK.IsChecked
|
||||||
UpdateEdge = $State.Controls.chkUpdateEdge.IsChecked
|
UpdateEdge = $State.Controls.chkUpdateEdge.IsChecked
|
||||||
@@ -461,6 +462,7 @@ function Update-UIFromConfig {
|
|||||||
Set-UIValue -ControlName 'chkRemoveFFU' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveFFU' -State $State
|
Set-UIValue -ControlName 'chkRemoveFFU' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveFFU' -State $State
|
||||||
Set-UIValue -ControlName 'chkRemoveApps' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveApps' -State $State
|
Set-UIValue -ControlName 'chkRemoveApps' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveApps' -State $State
|
||||||
Set-UIValue -ControlName 'chkRemoveUpdates' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveUpdates' -State $State
|
Set-UIValue -ControlName 'chkRemoveUpdates' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveUpdates' -State $State
|
||||||
|
Set-UIValue -ControlName 'chkRemoveDownloadedESD' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveDownloadedESD' -State $State
|
||||||
|
|
||||||
# Hyper-V Settings
|
# Hyper-V Settings
|
||||||
Select-VMSwitchFromConfig -State $State -ConfigContent $ConfigContent
|
Select-VMSwitchFromConfig -State $State -ConfigContent $ConfigContent
|
||||||
@@ -908,7 +910,8 @@ function Invoke-RestoreDefaults {
|
|||||||
-RemoveDrivers:$true `
|
-RemoveDrivers:$true `
|
||||||
-RemoveFFU:$true `
|
-RemoveFFU:$true `
|
||||||
-RemoveApps:$true `
|
-RemoveApps:$true `
|
||||||
-RemoveUpdates:$true
|
-RemoveUpdates:$true `
|
||||||
|
-RemoveDownloadedESD:$true
|
||||||
|
|
||||||
# Clear UI lists / state
|
# Clear UI lists / state
|
||||||
if ($null -ne $State.Data.allDriverModels) { $State.Data.allDriverModels.Clear() }
|
if ($null -ne $State.Data.allDriverModels) { $State.Data.allDriverModels.Clear() }
|
||||||
|
|||||||
@@ -212,6 +212,14 @@ function Save-DellDriversTask {
|
|||||||
Remove-Item $modelCabPath -Force -ErrorAction SilentlyContinue
|
Remove-Item $modelCabPath -Force -ErrorAction SilentlyContinue
|
||||||
if (-not (Test-Path $modelXmlPath)) { throw "Model XML not found after extraction: $modelXmlPath" }
|
if (-not (Test-Path $modelXmlPath)) { throw "Model XML not found after extraction: $modelXmlPath" }
|
||||||
|
|
||||||
|
# Track extracted model XML so cancel cleanup can remove it even if file timestamps are preserved from source metadata.
|
||||||
|
try {
|
||||||
|
Register-CurrentRunDownloadTarget -Destination $modelXmlPath
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Failed to register Dell model XML for current-run cleanup ($modelXmlPath): $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Selecting latest drivers...' }
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Selecting latest drivers...' }
|
||||||
$packages = Get-DellLatestDriverPackages -ModelXmlPath $modelXmlPath -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease
|
$packages = Get-DellLatestDriverPackages -ModelXmlPath $modelXmlPath -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,37 @@ function Get-DriverDisplayName {
|
|||||||
return "$($BaseName.Trim()) ($($Identifier.Trim()))"
|
return "$($BaseName.Trim()) ($($Identifier.Trim()))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-EffectiveDriverWindowsRelease {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[int]$WindowsRelease,
|
||||||
|
[string]$WindowsReleaseDisplay,
|
||||||
|
[string]$WindowsSku
|
||||||
|
)
|
||||||
|
|
||||||
|
# Normalize LTSC/LTSB UI release selections to client driver releases for OEM catalogs.
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($WindowsReleaseDisplay)) {
|
||||||
|
if (($WindowsReleaseDisplay -like 'Windows 10*') -and (($WindowsReleaseDisplay -like '*LTSB*') -or ($WindowsReleaseDisplay -like '*LTSC*'))) {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
if (($WindowsReleaseDisplay -like 'Windows 11*') -and ($WindowsReleaseDisplay -like '*LTSC*')) {
|
||||||
|
return 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use SKU-based fallback when display text is unavailable.
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($WindowsSku) -and $WindowsSku -like '*LTS*') {
|
||||||
|
if ($WindowsRelease -in 2016, 2019, 2021) {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
if ($WindowsRelease -eq 2024) {
|
||||||
|
return 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $WindowsRelease
|
||||||
|
}
|
||||||
|
|
||||||
function Convert-DriverItemToJsonModel {
|
function Convert-DriverItemToJsonModel {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
@@ -154,8 +185,20 @@ function Convert-DriverItemToJsonModel {
|
|||||||
# Get necessary values from UI or script scope
|
# Get necessary values from UI or script scope
|
||||||
$localDriversFolder = $State.Controls.txtDriversFolder.Text
|
$localDriversFolder = $State.Controls.txtDriversFolder.Text
|
||||||
$localWindowsRelease = $null
|
$localWindowsRelease = $null
|
||||||
|
$localWindowsReleaseDisplay = $null
|
||||||
if ($null -ne $State.Controls.cmbWindowsRelease.SelectedItem) {
|
if ($null -ne $State.Controls.cmbWindowsRelease.SelectedItem) {
|
||||||
$localWindowsRelease = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
$localWindowsRelease = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
||||||
|
$localWindowsReleaseDisplay = $State.Controls.cmbWindowsRelease.SelectedItem.Display
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve effective release used specifically for OEM driver operations.
|
||||||
|
$localWindowsSku = if ($null -ne $State.Controls.cmbWindowsSKU.SelectedItem) { [string]$State.Controls.cmbWindowsSKU.SelectedItem } else { $null }
|
||||||
|
$localDriverWindowsRelease = $localWindowsRelease
|
||||||
|
if ($null -ne $localWindowsRelease) {
|
||||||
|
$localDriverWindowsRelease = Get-EffectiveDriverWindowsRelease -WindowsRelease $localWindowsRelease -WindowsReleaseDisplay $localWindowsReleaseDisplay -WindowsSku $localWindowsSku
|
||||||
|
if ($localDriverWindowsRelease -ne $localWindowsRelease) {
|
||||||
|
WriteLog "Normalized WindowsRelease for model retrieval from $localWindowsRelease to $localDriverWindowsRelease (Display='$localWindowsReleaseDisplay', SKU='$localWindowsSku')."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get headers and user agent from Get-CoreStaticVariables
|
# Get headers and user agent from Get-CoreStaticVariables
|
||||||
@@ -173,7 +216,7 @@ function Convert-DriverItemToJsonModel {
|
|||||||
$rawModels = Get-MicrosoftDriversModelList -Headers $Headers -UserAgent $UserAgent -DriversFolder $localDriversFolder
|
$rawModels = Get-MicrosoftDriversModelList -Headers $Headers -UserAgent $UserAgent -DriversFolder $localDriversFolder
|
||||||
}
|
}
|
||||||
'Dell' {
|
'Dell' {
|
||||||
$rawModels = Get-DellDriversModelList -WindowsRelease $localWindowsRelease -DriversFolder $localDriversFolder -Make $SelectedMake
|
$rawModels = Get-DellDriversModelList -WindowsRelease $localDriverWindowsRelease -DriversFolder $localDriversFolder -Make $SelectedMake
|
||||||
}
|
}
|
||||||
'HP' {
|
'HP' {
|
||||||
$rawModels = Get-HPDriversModelList -DriversFolder $localDriversFolder -Make $SelectedMake
|
$rawModels = Get-HPDriversModelList -DriversFolder $localDriversFolder -Make $SelectedMake
|
||||||
@@ -344,7 +387,10 @@ function Save-DriversJson {
|
|||||||
[psobject]$State
|
[psobject]$State
|
||||||
)
|
)
|
||||||
WriteLog "Save-DriversJson function called."
|
WriteLog "Save-DriversJson function called."
|
||||||
$selectedDrivers = @($State.Controls.lstDriverModels.Items | Where-Object { $_.IsSelected })
|
|
||||||
|
# Save from the master model list so filtered-out selected rows are preserved.
|
||||||
|
$driverSelectionSource = if ($null -ne $State.Data.allDriverModels) { $State.Data.allDriverModels } else { $State.Controls.lstDriverModels.Items }
|
||||||
|
$selectedDrivers = @($driverSelectionSource | Where-Object { $_.IsSelected })
|
||||||
|
|
||||||
if (-not $selectedDrivers) {
|
if (-not $selectedDrivers) {
|
||||||
[System.Windows.MessageBox]::Show("No drivers selected to save.", "Save Drivers", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
[System.Windows.MessageBox]::Show("No drivers selected to save.", "Save Drivers", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
||||||
@@ -831,6 +877,12 @@ function Invoke-DownloadSelectedDrivers {
|
|||||||
|
|
||||||
$localDriversFolder = $State.Controls.txtDriversFolder.Text
|
$localDriversFolder = $State.Controls.txtDriversFolder.Text
|
||||||
$localWindowsRelease = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
$localWindowsRelease = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
||||||
|
$localWindowsReleaseDisplay = $State.Controls.cmbWindowsRelease.SelectedItem.Display
|
||||||
|
$localWindowsSku = if ($null -ne $State.Controls.cmbWindowsSKU.SelectedItem) { [string]$State.Controls.cmbWindowsSKU.SelectedItem } else { $null }
|
||||||
|
$localDriverWindowsRelease = Get-EffectiveDriverWindowsRelease -WindowsRelease $localWindowsRelease -WindowsReleaseDisplay $localWindowsReleaseDisplay -WindowsSku $localWindowsSku
|
||||||
|
if ($localDriverWindowsRelease -ne $localWindowsRelease) {
|
||||||
|
WriteLog "Normalized WindowsRelease for driver download from $localWindowsRelease to $localDriverWindowsRelease (Display='$localWindowsReleaseDisplay', SKU='$localWindowsSku')."
|
||||||
|
}
|
||||||
$localWindowsArch = $State.Controls.cmbWindowsArch.SelectedItem
|
$localWindowsArch = $State.Controls.cmbWindowsArch.SelectedItem
|
||||||
$localWindowsVersion = if ($null -ne $State.Controls.cmbWindowsVersion -and $null -ne $State.Controls.cmbWindowsVersion.SelectedItem) { $State.Controls.cmbWindowsVersion.SelectedItem } else { $null }
|
$localWindowsVersion = if ($null -ne $State.Controls.cmbWindowsVersion -and $null -ne $State.Controls.cmbWindowsVersion.SelectedItem) { $State.Controls.cmbWindowsVersion.SelectedItem } else { $null }
|
||||||
$coreStaticVars = Get-CoreStaticVariables
|
$coreStaticVars = Get-CoreStaticVariables
|
||||||
@@ -848,10 +900,10 @@ function Invoke-DownloadSelectedDrivers {
|
|||||||
WriteLog "Dell drivers selected. Ensuring Dell Catalog is up-to-date..."
|
WriteLog "Dell drivers selected. Ensuring Dell Catalog is up-to-date..."
|
||||||
try {
|
try {
|
||||||
$dellDriversFolder = Join-Path -Path $localDriversFolder -ChildPath "Dell"
|
$dellDriversFolder = Join-Path -Path $localDriversFolder -ChildPath "Dell"
|
||||||
$catalogBaseName = if ($localWindowsRelease -le 11) { "CatalogIndexPC" } else { "Catalog" }
|
$catalogBaseName = if ($localDriverWindowsRelease -le 11) { "CatalogIndexPC" } else { "Catalog" }
|
||||||
$dellCabFile = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).cab"
|
$dellCabFile = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).cab"
|
||||||
$dellCatalogXML = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).xml"
|
$dellCatalogXML = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).xml"
|
||||||
$catalogUrl = if ($localWindowsRelease -le 11) { "https://downloads.dell.com/catalog/CatalogIndexPC.cab" } else { "https://downloads.dell.com/catalog/Catalog.cab" }
|
$catalogUrl = if ($localDriverWindowsRelease -le 11) { "https://downloads.dell.com/catalog/CatalogIndexPC.cab" } else { "https://downloads.dell.com/catalog/Catalog.cab" }
|
||||||
|
|
||||||
$downloadCatalog = $true
|
$downloadCatalog = $true
|
||||||
if (Test-Path -Path $dellCatalogXML -PathType Leaf) {
|
if (Test-Path -Path $dellCatalogXML -PathType Leaf) {
|
||||||
@@ -891,7 +943,7 @@ function Invoke-DownloadSelectedDrivers {
|
|||||||
$preserveSource = ($State.Controls.chkUseDriversAsPEDrivers.IsChecked -and $State.Controls.chkCompressDriversToWIM.IsChecked)
|
$preserveSource = ($State.Controls.chkUseDriversAsPEDrivers.IsChecked -and $State.Controls.chkCompressDriversToWIM.IsChecked)
|
||||||
$taskArguments = @{
|
$taskArguments = @{
|
||||||
DriversFolder = $localDriversFolder
|
DriversFolder = $localDriversFolder
|
||||||
WindowsRelease = $localWindowsRelease
|
WindowsRelease = $localDriverWindowsRelease
|
||||||
WindowsArch = $localWindowsArch
|
WindowsArch = $localWindowsArch
|
||||||
WindowsVersion = $localWindowsVersion
|
WindowsVersion = $localWindowsVersion
|
||||||
Headers = $localHeaders
|
Headers = $localHeaders
|
||||||
|
|||||||
@@ -317,6 +317,9 @@ function Register-EventHandlers {
|
|||||||
Update-WindowsVersionCombo -selectedRelease $selectedReleaseValue -isoPath $localState.Controls.txtISOPath.Text -State $localState
|
Update-WindowsVersionCombo -selectedRelease $selectedReleaseValue -isoPath $localState.Controls.txtISOPath.Text -State $localState
|
||||||
Update-WindowsSkuCombo -State $localState
|
Update-WindowsSkuCombo -State $localState
|
||||||
Update-WindowsArchCombo -State $localState
|
Update-WindowsArchCombo -State $localState
|
||||||
|
|
||||||
|
# Re-evaluate Install Apps dependency when Windows release changes
|
||||||
|
Update-InstallAppsState -State $localState
|
||||||
})
|
})
|
||||||
|
|
||||||
$State.Controls.cmbWindowsVersion.Add_SelectionChanged({
|
$State.Controls.cmbWindowsVersion.Add_SelectionChanged({
|
||||||
@@ -369,6 +372,8 @@ function Register-EventHandlers {
|
|||||||
$State.Controls.chkUpdateOneDrive.Add_Unchecked($updateCheckboxHandler)
|
$State.Controls.chkUpdateOneDrive.Add_Unchecked($updateCheckboxHandler)
|
||||||
$State.Controls.chkUpdateLatestMSRT.Add_Checked($updateCheckboxHandler)
|
$State.Controls.chkUpdateLatestMSRT.Add_Checked($updateCheckboxHandler)
|
||||||
$State.Controls.chkUpdateLatestMSRT.Add_Unchecked($updateCheckboxHandler)
|
$State.Controls.chkUpdateLatestMSRT.Add_Unchecked($updateCheckboxHandler)
|
||||||
|
$State.Controls.chkUpdateLatestCU.Add_Checked($updateCheckboxHandler)
|
||||||
|
$State.Controls.chkUpdateLatestCU.Add_Unchecked($updateCheckboxHandler)
|
||||||
|
|
||||||
# Also attach the handler to the Office checkbox
|
# Also attach the handler to the Office checkbox
|
||||||
$State.Controls.chkInstallOffice.Add_Checked($updateCheckboxHandler)
|
$State.Controls.chkInstallOffice.Add_Checked($updateCheckboxHandler)
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ function Initialize-UIControls {
|
|||||||
$State.Controls.chkCleanupDeployISO = $window.FindName('chkCleanupDeployISO')
|
$State.Controls.chkCleanupDeployISO = $window.FindName('chkCleanupDeployISO')
|
||||||
$State.Controls.chkCleanupDrivers = $window.FindName('chkCleanupDrivers')
|
$State.Controls.chkCleanupDrivers = $window.FindName('chkCleanupDrivers')
|
||||||
$State.Controls.chkRemoveFFU = $window.FindName('chkRemoveFFU')
|
$State.Controls.chkRemoveFFU = $window.FindName('chkRemoveFFU')
|
||||||
|
$State.Controls.chkRemoveDownloadedESD = $window.FindName('chkRemoveDownloadedESD')
|
||||||
$State.Controls.txtDiskSize = $window.FindName('txtDiskSize')
|
$State.Controls.txtDiskSize = $window.FindName('txtDiskSize')
|
||||||
$State.Controls.txtMemory = $window.FindName('txtMemory')
|
$State.Controls.txtMemory = $window.FindName('txtMemory')
|
||||||
$State.Controls.txtProcessors = $window.FindName('txtProcessors')
|
$State.Controls.txtProcessors = $window.FindName('txtProcessors')
|
||||||
@@ -258,6 +259,7 @@ function Initialize-UIDefaults {
|
|||||||
$State.Controls.chkRemoveFFU.IsChecked = $State.Defaults.generalDefaults.RemoveFFU
|
$State.Controls.chkRemoveFFU.IsChecked = $State.Defaults.generalDefaults.RemoveFFU
|
||||||
$State.Controls.chkRemoveApps.IsChecked = $State.Defaults.generalDefaults.RemoveApps
|
$State.Controls.chkRemoveApps.IsChecked = $State.Defaults.generalDefaults.RemoveApps
|
||||||
$State.Controls.chkRemoveUpdates.IsChecked = $State.Defaults.generalDefaults.RemoveUpdates
|
$State.Controls.chkRemoveUpdates.IsChecked = $State.Defaults.generalDefaults.RemoveUpdates
|
||||||
|
$State.Controls.chkRemoveDownloadedESD.IsChecked = $State.Defaults.generalDefaults.RemoveDownloadedESD
|
||||||
$State.Controls.chkVerbose.IsChecked = $State.Defaults.generalDefaults.Verbose
|
$State.Controls.chkVerbose.IsChecked = $State.Defaults.generalDefaults.Verbose
|
||||||
$State.Controls.usbSection.Visibility = if ($State.Controls.chkBuildUSBDriveEnable.IsChecked) { 'Visible' } else { 'Collapsed' }
|
$State.Controls.usbSection.Visibility = if ($State.Controls.chkBuildUSBDriveEnable.IsChecked) { 'Visible' } else { 'Collapsed' }
|
||||||
$State.Controls.usbSelectionPanel.Visibility = if ($State.Controls.chkSelectSpecificUSBDrives.IsChecked) { 'Visible' } else { 'Collapsed' }
|
$State.Controls.usbSelectionPanel.Visibility = if ($State.Controls.chkSelectSpecificUSBDrives.IsChecked) { 'Visible' } else { 'Collapsed' }
|
||||||
@@ -366,8 +368,8 @@ function Initialize-DynamicUIElements {
|
|||||||
$driverModelsGridView = New-Object System.Windows.Controls.GridView
|
$driverModelsGridView = New-Object System.Windows.Controls.GridView
|
||||||
$State.Controls.lstDriverModels.View = $driverModelsGridView # Assign GridView to ListView first
|
$State.Controls.lstDriverModels.View = $driverModelsGridView # Assign GridView to ListView first
|
||||||
|
|
||||||
# Add the selectable column using the new function
|
# Add the selectable column and scope header select-all to visible filtered rows.
|
||||||
Add-SelectableGridViewColumn -ListView $State.Controls.lstDriverModels -State $State -HeaderCheckBoxKeyName "chkSelectAllDriverModels" -ColumnWidth 70
|
Add-SelectableGridViewColumn -ListView $State.Controls.lstDriverModels -State $State -HeaderCheckBoxKeyName "chkSelectAllDriverModels" -ColumnWidth 70 -HeaderSelectionAffectsVisibleItemsOnly
|
||||||
|
|
||||||
# Add other sortable columns with left-aligned headers
|
# Add other sortable columns with left-aligned headers
|
||||||
Add-SortableColumn -gridView $driverModelsGridView -header "Make" -binding "Make" -width 100 -headerHorizontalAlignment Left
|
Add-SortableColumn -gridView $driverModelsGridView -header "Make" -binding "Make" -width 100 -headerHorizontalAlignment Left
|
||||||
|
|||||||
@@ -329,7 +329,8 @@ function Add-SelectableGridViewColumn {
|
|||||||
[string]$HeaderCheckBoxKeyName,
|
[string]$HeaderCheckBoxKeyName,
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[double]$ColumnWidth,
|
[double]$ColumnWidth,
|
||||||
[string]$IsSelectedPropertyName = "IsSelected"
|
[string]$IsSelectedPropertyName = "IsSelected",
|
||||||
|
[switch]$HeaderSelectionAffectsVisibleItemsOnly
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure the ListView has a GridView
|
# Ensure the ListView has a GridView
|
||||||
@@ -343,10 +344,11 @@ function Add-SelectableGridViewColumn {
|
|||||||
$headerCheckBox = New-Object System.Windows.Controls.CheckBox
|
$headerCheckBox = New-Object System.Windows.Controls.CheckBox
|
||||||
$headerCheckBox.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
|
$headerCheckBox.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
|
||||||
|
|
||||||
# MODIFICATION: Store the actual ListView object in the header's Tag
|
# Store header metadata, including whether select-all should only affect visible rows.
|
||||||
$headerTagObject = [PSCustomObject]@{
|
$headerTagObject = [PSCustomObject]@{
|
||||||
PropertyName = $IsSelectedPropertyName
|
PropertyName = $IsSelectedPropertyName
|
||||||
ListViewControl = $ListView
|
ListViewControl = $ListView
|
||||||
|
HeaderSelectionAffectsVisibleItemsOnly = [bool]$HeaderSelectionAffectsVisibleItemsOnly
|
||||||
}
|
}
|
||||||
$headerCheckBox.Tag = $headerTagObject
|
$headerCheckBox.Tag = $headerTagObject
|
||||||
|
|
||||||
@@ -356,8 +358,24 @@ function Add-SelectableGridViewColumn {
|
|||||||
$localPropertyName = $tagData.PropertyName
|
$localPropertyName = $tagData.PropertyName
|
||||||
$actualListView = $tagData.ListViewControl
|
$actualListView = $tagData.ListViewControl
|
||||||
|
|
||||||
$collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items }
|
# Select either visible view items only (filtered scope) or the full backing list.
|
||||||
if ($null -ne $collectionToUpdate) {
|
$collectionToUpdate = @()
|
||||||
|
if ($tagData.HeaderSelectionAffectsVisibleItemsOnly -and $null -ne $actualListView.ItemsSource) {
|
||||||
|
$collectionView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($actualListView.ItemsSource)
|
||||||
|
if ($null -ne $collectionView) {
|
||||||
|
foreach ($visibleItem in $collectionView) {
|
||||||
|
$collectionToUpdate += $visibleItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($null -ne $actualListView.ItemsSource) {
|
||||||
|
$collectionToUpdate = @($actualListView.ItemsSource)
|
||||||
|
}
|
||||||
|
elseif ($actualListView.HasItems) {
|
||||||
|
$collectionToUpdate = @($actualListView.Items)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($collectionToUpdate.Count -gt 0) {
|
||||||
foreach ($item in $collectionToUpdate) { $item.$($localPropertyName) = $true }
|
foreach ($item in $collectionToUpdate) { $item.$($localPropertyName) = $true }
|
||||||
$actualListView.Items.Refresh()
|
$actualListView.Items.Refresh()
|
||||||
}
|
}
|
||||||
@@ -370,8 +388,24 @@ function Add-SelectableGridViewColumn {
|
|||||||
$localPropertyName = $tagData.PropertyName
|
$localPropertyName = $tagData.PropertyName
|
||||||
$actualListView = $tagData.ListViewControl
|
$actualListView = $tagData.ListViewControl
|
||||||
|
|
||||||
$collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items }
|
# Clear either visible view items only (filtered scope) or the full backing list.
|
||||||
if ($null -ne $collectionToUpdate) {
|
$collectionToUpdate = @()
|
||||||
|
if ($tagData.HeaderSelectionAffectsVisibleItemsOnly -and $null -ne $actualListView.ItemsSource) {
|
||||||
|
$collectionView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($actualListView.ItemsSource)
|
||||||
|
if ($null -ne $collectionView) {
|
||||||
|
foreach ($visibleItem in $collectionView) {
|
||||||
|
$collectionToUpdate += $visibleItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($null -ne $actualListView.ItemsSource) {
|
||||||
|
$collectionToUpdate = @($actualListView.ItemsSource)
|
||||||
|
}
|
||||||
|
elseif ($actualListView.HasItems) {
|
||||||
|
$collectionToUpdate = @($actualListView.Items)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($collectionToUpdate.Count -gt 0) {
|
||||||
foreach ($item in $collectionToUpdate) { $item.$($localPropertyName) = $false }
|
foreach ($item in $collectionToUpdate) { $item.$($localPropertyName) = $false }
|
||||||
$actualListView.Items.Refresh()
|
$actualListView.Items.Refresh()
|
||||||
}
|
}
|
||||||
@@ -446,24 +480,38 @@ function Update-SelectAllHeaderCheckBoxState {
|
|||||||
[System.Windows.Controls.CheckBox]$HeaderCheckBox
|
[System.Windows.Controls.CheckBox]$HeaderCheckBox
|
||||||
)
|
)
|
||||||
|
|
||||||
$collectionToInspect = $null
|
# Determine whether this header should evaluate only visible (filtered) rows.
|
||||||
if ($null -ne $ListView.ItemsSource) {
|
$inspectVisibleItemsOnly = $false
|
||||||
|
if ($null -ne $HeaderCheckBox.Tag -and $null -ne $HeaderCheckBox.Tag.PSObject.Properties['HeaderSelectionAffectsVisibleItemsOnly']) {
|
||||||
|
$inspectVisibleItemsOnly = [bool]$HeaderCheckBox.Tag.HeaderSelectionAffectsVisibleItemsOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build the collection to inspect based on scope (visible view vs full source).
|
||||||
|
$collectionToInspect = @()
|
||||||
|
if ($inspectVisibleItemsOnly -and $null -ne $ListView.ItemsSource) {
|
||||||
|
$collectionView = [System.Windows.Data.CollectionViewSource]::GetDefaultView($ListView.ItemsSource)
|
||||||
|
if ($null -ne $collectionView) {
|
||||||
|
foreach ($visibleItem in $collectionView) {
|
||||||
|
$collectionToInspect += $visibleItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($null -ne $ListView.ItemsSource) {
|
||||||
$collectionToInspect = @($ListView.ItemsSource)
|
$collectionToInspect = @($ListView.ItemsSource)
|
||||||
}
|
}
|
||||||
elseif ($ListView.HasItems) {
|
elseif ($ListView.HasItems) {
|
||||||
# Check if Items collection has items and ItemsSource is null
|
|
||||||
$collectionToInspect = @($ListView.Items)
|
$collectionToInspect = @($ListView.Items)
|
||||||
}
|
}
|
||||||
|
|
||||||
# If no items to inspect (either ItemsSource was null and Items was empty, or ItemsSource was empty)
|
# If no items are available in the selected scope, force unchecked.
|
||||||
if ($null -eq $collectionToInspect -or $collectionToInspect.Count -eq 0) {
|
if ($collectionToInspect.Count -eq 0) {
|
||||||
$HeaderCheckBox.IsChecked = $false
|
$HeaderCheckBox.IsChecked = $false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$selectedCount = ($collectionToInspect | Where-Object { $_.IsSelected }).Count
|
$selectedCount = ($collectionToInspect | Where-Object { $_.IsSelected }).Count
|
||||||
WriteLog "Update-SelectAllHeaderCheckBoxState: Selected count is $selectedCount for ListView '$($ListView.Name)'."
|
WriteLog "Update-SelectAllHeaderCheckBoxState: Selected count is $selectedCount for ListView '$($ListView.Name)'."
|
||||||
$totalItemCount = $collectionToInspect.Count # Get the total count from the collection being inspected
|
$totalItemCount = $collectionToInspect.Count
|
||||||
WriteLog "Update-SelectAllHeaderCheckBoxState: Total item count is $totalItemCount for ListView '$($ListView.Name)'."
|
WriteLog "Update-SelectAllHeaderCheckBoxState: Total item count is $totalItemCount for ListView '$($ListView.Name)'."
|
||||||
|
|
||||||
if ($totalItemCount -eq 0) {
|
if ($totalItemCount -eq 0) {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ function Get-GeneralDefaults {
|
|||||||
RemoveFFU = $false
|
RemoveFFU = $false
|
||||||
RemoveApps = $false
|
RemoveApps = $false
|
||||||
RemoveUpdates = $false
|
RemoveUpdates = $false
|
||||||
|
RemoveDownloadedESD = $true
|
||||||
# Hyper-V Settings Defaults
|
# Hyper-V Settings Defaults
|
||||||
VMHostIPAddress = ""
|
VMHostIPAddress = ""
|
||||||
DiskSizeGB = 50
|
DiskSizeGB = 50
|
||||||
@@ -320,6 +321,23 @@ function Update-ApplicationPanelVisibility {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Function to identify whether current Windows release selection is Windows 10 LTSB/LTSC
|
||||||
|
function Test-IsWindows10LtscReleaseSelection {
|
||||||
|
param([PSCustomObject]$State)
|
||||||
|
|
||||||
|
$releaseItem = $State.Controls.cmbWindowsRelease.SelectedItem
|
||||||
|
if ($null -eq $releaseItem) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$releaseDisplay = [string]$releaseItem.Display
|
||||||
|
if ([string]::IsNullOrWhiteSpace($releaseDisplay)) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (($releaseDisplay -like 'Windows 10*') -and (($releaseDisplay -like '*LTSB*') -or ($releaseDisplay -like '*LTSC*')))
|
||||||
|
}
|
||||||
|
|
||||||
# Function to manage the state of the main "Install Apps" checkbox based on selections in Updates/Office
|
# Function to manage the state of the main "Install Apps" checkbox based on selections in Updates/Office
|
||||||
function Update-InstallAppsState {
|
function Update-InstallAppsState {
|
||||||
param([PSCustomObject]$State)
|
param([PSCustomObject]$State)
|
||||||
@@ -327,11 +345,16 @@ function Update-InstallAppsState {
|
|||||||
$installAppsChk = $State.Controls.chkInstallApps
|
$installAppsChk = $State.Controls.chkInstallApps
|
||||||
$installOfficeChk = $State.Controls.chkInstallOffice
|
$installOfficeChk = $State.Controls.chkInstallOffice
|
||||||
|
|
||||||
|
# Determine if Windows 10 LTSB/LTSC + Update Latest CU is selected
|
||||||
|
$isWindows10LtscRelease = Test-IsWindows10LtscReleaseSelection -State $State
|
||||||
|
$isLtscCuChecked = $State.Controls.chkUpdateLatestCU.IsChecked -and $isWindows10LtscRelease
|
||||||
|
|
||||||
# Determine if any checkbox that forces "Install Apps" is checked
|
# Determine if any checkbox that forces "Install Apps" is checked
|
||||||
$anyUpdateChecked = $State.Controls.chkUpdateLatestDefender.IsChecked -or `
|
$anyUpdateChecked = $State.Controls.chkUpdateLatestDefender.IsChecked -or `
|
||||||
$State.Controls.chkUpdateEdge.IsChecked -or `
|
$State.Controls.chkUpdateEdge.IsChecked -or `
|
||||||
$State.Controls.chkUpdateOneDrive.IsChecked -or `
|
$State.Controls.chkUpdateOneDrive.IsChecked -or `
|
||||||
$State.Controls.chkUpdateLatestMSRT.IsChecked
|
$State.Controls.chkUpdateLatestMSRT.IsChecked -or `
|
||||||
|
$isLtscCuChecked
|
||||||
|
|
||||||
$isForced = $anyUpdateChecked -or $installOfficeChk.IsChecked
|
$isForced = $anyUpdateChecked -or $installOfficeChk.IsChecked
|
||||||
|
|
||||||
|
|||||||
@@ -835,7 +835,7 @@ $LogFileName = 'ScriptLog.txt'
|
|||||||
$USBDrive = Get-USBDrive
|
$USBDrive = Get-USBDrive
|
||||||
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
||||||
$LogFile = $USBDrive + $LogFilename
|
$LogFile = $USBDrive + $LogFilename
|
||||||
$version = '2602.1Preview'
|
$version = '2603.1'
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
WriteLog "Script version: $version"
|
WriteLog "Script version: $version"
|
||||||
|
|
||||||
|
|||||||
@@ -18,88 +18,14 @@ And the best part: **it takes less than two minutes** to apply the image, even w
|
|||||||
|
|
||||||
The Full-Flash update (FFU) process can automatically download the latest release of Windows 11, the updates mentioned above, and creates a USB drive that can be used to quickly reimage a machine.
|
The Full-Flash update (FFU) process can automatically download the latest release of Windows 11, the updates mentioned above, and creates a USB drive that can be used to quickly reimage a machine.
|
||||||
|
|
||||||
# Updates
|
|
||||||
|
|
||||||
2509.1 has been released to preview! This is a major update that brings a new user interface to preview.
|
|
||||||
|
|
||||||
Docs are coming, but will take a bit to write them. The youtube video is a must watch for a complete demo on how to use the UI and the changes made to apps (InstallAppsAndSysprep.cmd is gone) and drivers. I'll be recording a more formalized deep dive with slides that go a bit deeper into how things work, but the UI walkthrough should get most people going.
|
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
- Download the latest [release](https://github.com/rbalsleyMSFT/FFU/releases)
|
If you're new, check out the [Quick Start Guide](https://rbalsleymsft.github.io/FFU/quickstart.html).
|
||||||
- Extract the FFUDevelopment folder from the ZIP file (recommend to C:\FFUDevelopment)
|
|
||||||
- Watch the Youtube video (updated docs for the UI coming soon)
|
|
||||||
|
|
||||||
## YouTube Detailed Walkthrough
|
This will be the fastest way to create your first FFU. There's a new [FFU Builder Quickstart Youtube video](https://youtu.be/kOIK5OmDugc) based on the 2602.1 UI Preview release.
|
||||||
|
|
||||||
Here's a detailed overview of the new UI process.
|
## Older Youtube Videos
|
||||||
|
|
||||||
[](https://youtu.be/oozG1aVcg9M "Reimage Windows Fast with FFU Builder 2507.1 Preview")
|
[2507.1 UI Preview Video](https://www.youtube.com/watch?v=oozG1aVcg9M) - First UI Preview release video. This goes deeper than the quick start video, but is missing some features that have been added since 2507.1 was released.
|
||||||
|
|
||||||
Chapters:
|
[2407.2 Video](https://www.youtube.com/watch?v=rqXRbgeeKSQ) - This was the main deep-dive video on FFU Builder (before it had that name). This is a good deep dive into how the BuildFFUVM.ps1 script works, but a lot has changed since that build.
|
||||||
|
|
||||||
[00:00](https://www.youtube.com/watch?v=oozG1aVcg9M&t=0s) Begin
|
|
||||||
|
|
||||||
[01:07](https://www.youtube.com/watch?v=oozG1aVcg9M&t=67s) Prereqs
|
|
||||||
|
|
||||||
[06:32](https://www.youtube.com/watch?v=oozG1aVcg9M&t=392s) Demo Begins
|
|
||||||
|
|
||||||
[07:16](https://www.youtube.com/watch?v=oozG1aVcg9M&t=436s) Running the BuildFFUVM_UI.ps1 script
|
|
||||||
|
|
||||||
[08:15](https://www.youtube.com/watch?v=oozG1aVcg9M&t=495s) UI Overview
|
|
||||||
|
|
||||||
[10:13](https://www.youtube.com/watch?v=oozG1aVcg9M&t=613s) Hyper-V Settings
|
|
||||||
|
|
||||||
[16:04](https://www.youtube.com/watch?v=oozG1aVcg9M&t=964s) Windows Settings
|
|
||||||
|
|
||||||
[22:35](https://www.youtube.com/watch?v=oozG1aVcg9M&t=1355s) Updates
|
|
||||||
|
|
||||||
[24:49](https://www.youtube.com/watch?v=oozG1aVcg9M&t=1489s) Applications
|
|
||||||
|
|
||||||
[29:39](https://www.youtube.com/watch?v=oozG1aVcg9M&t=1779s) Install Winget Applications
|
|
||||||
|
|
||||||
[45:29](https://www.youtube.com/watch?v=oozG1aVcg9M&t=2729s) Bring Your Own Applications
|
|
||||||
|
|
||||||
[54:14](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3254s) Apps Script Variables
|
|
||||||
|
|
||||||
[57:43](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3463s) M365 Apps/Office
|
|
||||||
|
|
||||||
[59:01](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3541s) Drivers
|
|
||||||
|
|
||||||
[01:01:22](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3682s) Drivers.json example
|
|
||||||
|
|
||||||
[01:02:07](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3727s) DriverMapping.json explanation
|
|
||||||
|
|
||||||
[01:06:08](https://www.youtube.com/watch?v=oozG1aVcg9M&t=3968s) Driver WIM Compression
|
|
||||||
|
|
||||||
[01:10:50](https://www.youtube.com/watch?v=oozG1aVcg9M&t=4250s) Build
|
|
||||||
|
|
||||||
[01:12:41](https://www.youtube.com/watch?v=oozG1aVcg9M&t=4361s) Build USB Drive
|
|
||||||
|
|
||||||
[01:20:07](https://www.youtube.com/watch?v=oozG1aVcg9M&t=4807s) Monitor
|
|
||||||
|
|
||||||
[01:20:32](https://www.youtube.com/watch?v=oozG1aVcg9M&t=4832s) Setting up the Demo Build
|
|
||||||
|
|
||||||
[01:24:10](https://www.youtube.com/watch?v=oozG1aVcg9M&t=5050s) Save/Load Config Files
|
|
||||||
|
|
||||||
[01:25:11](https://www.youtube.com/watch?v=oozG1aVcg9M&t=5111s) Kicking off the Demo Build/Going over the monitor tab
|
|
||||||
|
|
||||||
[01:32:26](https://www.youtube.com/watch?v=oozG1aVcg9M&t=5546s) Demoing the new FFU Builder Orchestrator
|
|
||||||
|
|
||||||
[01:35:25](https://www.youtube.com/watch?v=oozG1aVcg9M&t=5725s) New captureffu.ps1 console output
|
|
||||||
|
|
||||||
[01:42:29](https://www.youtube.com/watch?v=oozG1aVcg9M&t=6149s) Demo Build Complete
|
|
||||||
|
|
||||||
[01:42:42](https://www.youtube.com/watch?v=oozG1aVcg9M&t=6162s) How to configure a VM to test your newly built FFU
|
|
||||||
|
|
||||||
[01:48:58](https://www.youtube.com/watch?v=oozG1aVcg9M&t=6538s) The moment of truth: What does the new deployment experience look like?
|
|
||||||
|
|
||||||
[01:53:13](https://www.youtube.com/watch?v=oozG1aVcg9M&t=6793s) How to bypass OOBE using a provisioning package
|
|
||||||
|
|
||||||
[01:55:49](https://www.youtube.com/watch?v=oozG1aVcg9M&t=6949s) Preview Focus Areas
|
|
||||||
|
|
||||||
[02:04:04](https://www.youtube.com/watch?v=oozG1aVcg9M&t=7444s) Known Issues/Things to fix before GA
|
|
||||||
|
|
||||||
[02:05:38](https://www.youtube.com/watch?v=oozG1aVcg9M&t=7538s) Providing Feedback
|
|
||||||
|
|
||||||
[02:06:43](https://www.youtube.com/watch?v=oozG1aVcg9M&t=7603s) Thank you
|
|
||||||
|
|||||||
+3
-1
@@ -4,10 +4,12 @@ remote_theme: just-the-docs/just-the-docs@v0.10.1
|
|||||||
plugins:
|
plugins:
|
||||||
- jekyll-remote-theme
|
- jekyll-remote-theme
|
||||||
- jekyll-seo-tag
|
- jekyll-seo-tag
|
||||||
- jekyll-sitemap
|
|
||||||
|
|
||||||
search_enabled: true
|
search_enabled: true
|
||||||
|
|
||||||
|
# Canonical host for absolute URLs (sitemap/robots/canonical tags)
|
||||||
|
url: "https://rbalsleymsft.github.io"
|
||||||
|
|
||||||
# Because you’ll publish as a project site at /FFU
|
# Because you’ll publish as a project site at /FFU
|
||||||
baseurl: "/FFU"
|
baseurl: "/FFU"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
{% if page.url == '/' or page.url == '/index.html' %}
|
||||||
|
<meta name="google-site-verification" content="2O8GqDcQF_fvyvZjeTz-YlTaN2p62FfWd9w-xHU4Zbc" />
|
||||||
|
{% endif %}
|
||||||
|
{% if page.url == '/' or page.url == '/index.html' %}
|
||||||
|
<meta name="google-site-verification" content="2O8GqDcQF_fvyvZjeTz-YlTaN2p62FfWd9w-xHU4Zbc" />
|
||||||
|
{% endif %}
|
||||||
<!-- docs/_includes/head_custom.html -->
|
<!-- docs/_includes/head_custom.html -->
|
||||||
<style>
|
<style>
|
||||||
/* Layout: remove Just-the-Docs "centered narrow" constraints on wide screens */
|
/* Layout: remove Just-the-Docs "centered narrow" constraints on wide screens */
|
||||||
@@ -57,6 +63,13 @@
|
|||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Lists: increase indentation (Learn-like) */
|
||||||
|
/* Just-the-Docs draws bullets/numbers via ::before with negative offsets */
|
||||||
|
.main-content ul,
|
||||||
|
.main-content ol {
|
||||||
|
padding-left: 2.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.main-content h1,
|
.main-content h1,
|
||||||
.main-content h2,
|
.main-content h2,
|
||||||
.main-content h3 {
|
.main-content h3 {
|
||||||
@@ -132,8 +145,16 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
justify-self: stretch;
|
justify-self: stretch;
|
||||||
|
|
||||||
/* Safety net: if anything still overflows, don't let it render under the TOC */
|
/* Keep heading permalink icons visible */
|
||||||
overflow-x: hidden;
|
/* (Just-the-Docs positions .anchor-heading to the left at desktop widths) */
|
||||||
|
overflow-x: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safety net: prevent long code/tables from overlapping the TOC */
|
||||||
|
.main-content-wrap.has-page-toc .main-content pre,
|
||||||
|
.main-content-wrap.has-page-toc .main-content .highlighter-rouge,
|
||||||
|
.main-content-wrap.has-page-toc .main-content .table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TOC always stays in the right column */
|
/* TOC always stays in the right column */
|
||||||
@@ -190,6 +211,77 @@
|
|||||||
border-left-color: #2563eb;
|
border-left-color: #2563eb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Inline "In this article" TOC (narrow viewports; Learn-like) */
|
||||||
|
.page-toc.page-toc--inline {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 1px solid #eeebee;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #27262b;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__list {
|
||||||
|
list-style: none !important;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__item {
|
||||||
|
list-style: none !important;
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__item::marker {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__item--h3 {
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__link {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
padding: 0.125rem 0 0.125rem 0.75rem;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc--inline .page-toc__link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc__item.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Just-the-Docs renders UL bullets via li::before; disable for inline TOC */
|
||||||
|
.page-toc--inline ul > li::before,
|
||||||
|
.page-toc--inline .page-toc__list > li::before,
|
||||||
|
.page-toc--inline .page-toc__item::before {
|
||||||
|
content: "" !important;
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc__toggle {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0.25rem 0 0 0.75rem;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-toc__toggle:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<meta name="ffu-right-toc" content="{% if page.right_toc == false %}false{% else %}true{% endif %}">
|
<meta name="ffu-right-toc" content="{% if page.right_toc == false %}false{% else %}true{% endif %}">
|
||||||
@@ -197,3 +289,4 @@
|
|||||||
<script src="{{ '/assets/js/vendor/medium-zoom.min.js' | relative_url }}" defer></script>
|
<script src="{{ '/assets/js/vendor/medium-zoom.min.js' | relative_url }}" defer></script>
|
||||||
<script src="{{ '/assets/js/image-zoom.js' | relative_url }}" defer></script>
|
<script src="{{ '/assets/js/image-zoom.js' | relative_url }}" defer></script>
|
||||||
<script src="{{ '/assets/js/page-toc.js' | relative_url }}" defer></script>
|
<script src="{{ '/assets/js/page-toc.js' | relative_url }}" defer></script>
|
||||||
|
<script src="{{ '/assets/js/external-links.js' | relative_url }}" defer></script>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function HasToken(tokens, token) {
|
||||||
|
for (var i = 0; i < tokens.length; i++) {
|
||||||
|
if (tokens[i] === token) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddRelToken(anchor, token) {
|
||||||
|
var rel = (anchor.getAttribute('rel') || '').trim();
|
||||||
|
var tokens = rel ? rel.split(/\s+/) : [];
|
||||||
|
|
||||||
|
if (!HasToken(tokens, token)) {
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
anchor.setAttribute('rel', tokens.join(' ').trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsExternalHttpLink(url) {
|
||||||
|
if (!url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.origin !== window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function InitExternalLinksNewTab() {
|
||||||
|
var mainContent = document.querySelector('.main-content');
|
||||||
|
if (!mainContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anchors = mainContent.querySelectorAll('a[href]');
|
||||||
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
|
var anchor = anchors[i];
|
||||||
|
var href = (anchor.getAttribute('href') || '').trim();
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (href.charAt(0) === '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (href.indexOf('mailto:') === 0 || href.indexOf('tel:') === 0 || href.indexOf('javascript:') === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = null;
|
||||||
|
try {
|
||||||
|
url = new URL(href, window.location.href);
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsExternalHttpLink(url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = (anchor.getAttribute('target') || '').trim();
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
anchor.setAttribute('target', '_blank');
|
||||||
|
target = '_blank';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target === '_blank') {
|
||||||
|
AddRelToken(anchor, 'noopener');
|
||||||
|
AddRelToken(anchor, 'noreferrer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', InitExternalLinksNewTab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitExternalLinksNewTab();
|
||||||
|
})();
|
||||||
+158
-41
@@ -1,6 +1,10 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var scrollSpyDispose = null;
|
||||||
|
var resizeReinitTimerId = null;
|
||||||
|
var inlineMaxVisibleItems = 4;
|
||||||
|
|
||||||
function IsRightTocEnabled() {
|
function IsRightTocEnabled() {
|
||||||
var meta = document.querySelector('meta[name="ffu-right-toc"]');
|
var meta = document.querySelector('meta[name="ffu-right-toc"]');
|
||||||
if (meta && meta.content && meta.content.toLowerCase() === 'false') {
|
if (meta && meta.content && meta.content.toLowerCase() === 'false') {
|
||||||
@@ -18,6 +22,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RemoveExistingToc() {
|
||||||
|
if (scrollSpyDispose) {
|
||||||
|
scrollSpyDispose();
|
||||||
|
scrollSpyDispose = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingTocs = document.querySelectorAll('.page-toc');
|
||||||
|
for (var i = 0; i < existingTocs.length; i++) {
|
||||||
|
existingTocs[i].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrap = document.querySelector('.main-content-wrap');
|
||||||
|
if (wrap) {
|
||||||
|
wrap.classList.remove('has-page-toc');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function InsertInlineToc(main, toc) {
|
||||||
|
if (!main || !toc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var title = main.querySelector('h1');
|
||||||
|
if (title && title.parentNode === main) {
|
||||||
|
if (title.nextSibling) {
|
||||||
|
main.insertBefore(toc, title.nextSibling);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.appendChild(toc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main.firstChild) {
|
||||||
|
main.insertBefore(toc, main.firstChild);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.appendChild(toc);
|
||||||
|
}
|
||||||
|
|
||||||
function GetHeadings(container) {
|
function GetHeadings(container) {
|
||||||
var headings = container.querySelectorAll('h2, h3');
|
var headings = container.querySelectorAll('h2, h3');
|
||||||
var results = [];
|
var results = [];
|
||||||
@@ -49,9 +94,13 @@
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BuildToc(headings) {
|
function BuildToc(headings, options) {
|
||||||
|
var variant = (options && options.variant) ? options.variant : 'right';
|
||||||
|
var maxVisible = (options && options.maxVisible) ? options.maxVisible : 0;
|
||||||
|
var isInline = 'inline' === variant;
|
||||||
|
|
||||||
var nav = document.createElement('nav');
|
var nav = document.createElement('nav');
|
||||||
nav.className = 'page-toc';
|
nav.className = 'page-toc' + (isInline ? ' page-toc--inline' : '');
|
||||||
nav.setAttribute('aria-label', 'On this page');
|
nav.setAttribute('aria-label', 'On this page');
|
||||||
|
|
||||||
var title = document.createElement('div');
|
var title = document.createElement('div');
|
||||||
@@ -61,6 +110,7 @@
|
|||||||
|
|
||||||
var list = document.createElement('ul');
|
var list = document.createElement('ul');
|
||||||
list.className = 'page-toc__list';
|
list.className = 'page-toc__list';
|
||||||
|
list.id = 'page-toc-list';
|
||||||
|
|
||||||
for (var i = 0; i < headings.length; i++) {
|
for (var i = 0; i < headings.length; i++) {
|
||||||
var item = headings[i];
|
var item = headings[i];
|
||||||
@@ -75,13 +125,61 @@
|
|||||||
|
|
||||||
li.appendChild(a);
|
li.appendChild(a);
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
|
|
||||||
|
if (isInline && maxVisible > 0 && i >= maxVisible) {
|
||||||
|
li.classList.add('is-hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.appendChild(list);
|
nav.appendChild(list);
|
||||||
|
|
||||||
|
if (isInline && maxVisible > 0 && headings.length > maxVisible) {
|
||||||
|
var hiddenCount = headings.length - maxVisible;
|
||||||
|
var isExpanded = false;
|
||||||
|
|
||||||
|
var toggle = document.createElement('button');
|
||||||
|
toggle.type = 'button';
|
||||||
|
toggle.className = 'page-toc__toggle';
|
||||||
|
toggle.setAttribute('aria-controls', list.id);
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
|
function SetToggleText() {
|
||||||
|
if (isExpanded) {
|
||||||
|
toggle.textContent = 'Show less';
|
||||||
|
} else {
|
||||||
|
toggle.textContent = 'Show ' + hiddenCount + ' more';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetHiddenState() {
|
||||||
|
var items = list.querySelectorAll('.page-toc__item');
|
||||||
|
for (var j = 0; j < items.length; j++) {
|
||||||
|
if (j >= maxVisible) {
|
||||||
|
if (isExpanded) {
|
||||||
|
items[j].classList.remove('is-hidden');
|
||||||
|
} else {
|
||||||
|
items[j].classList.add('is-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
|
||||||
|
SetToggleText();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.addEventListener('click', function () {
|
||||||
|
isExpanded = !isExpanded;
|
||||||
|
SetHiddenState();
|
||||||
|
});
|
||||||
|
|
||||||
|
SetHiddenState();
|
||||||
|
nav.appendChild(toggle);
|
||||||
|
}
|
||||||
|
|
||||||
return nav;
|
return nav;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetActiveTocLink(toc, activeId) {
|
function SetActiveTocLink(toc, activeId, keepVisibleInPanel) {
|
||||||
if (!toc) {
|
if (!toc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,12 +193,14 @@
|
|||||||
if (isActive) {
|
if (isActive) {
|
||||||
link.classList.add('is-active');
|
link.classList.add('is-active');
|
||||||
|
|
||||||
/* Keep the active item visible inside the TOC panel */
|
if (keepVisibleInPanel) {
|
||||||
|
/* Keep the active item visible inside the TOC panel (desktop/right TOC only) */
|
||||||
try {
|
try {
|
||||||
link.scrollIntoView({ block: 'nearest' });
|
link.scrollIntoView({ block: 'nearest' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
link.scrollIntoView();
|
link.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
link.classList.remove('is-active');
|
link.classList.remove('is-active');
|
||||||
}
|
}
|
||||||
@@ -109,12 +209,12 @@
|
|||||||
|
|
||||||
function SetupScrollSpy(main, toc, headings) {
|
function SetupScrollSpy(main, toc, headings) {
|
||||||
if (!main || !toc || !headings || headings.length < 1) {
|
if (!main || !toc || !headings || headings.length < 1) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollspy is desktop-only; on mobile it can cause "fighting" scroll behavior */
|
/* Scrollspy is desktop-only */
|
||||||
if (!IsDesktopViewport()) {
|
if (!IsDesktopViewport()) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var headingElements = [];
|
var headingElements = [];
|
||||||
@@ -126,7 +226,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (headingElements.length < 1) {
|
if (headingElements.length < 1) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeId = null;
|
var activeId = null;
|
||||||
@@ -143,7 +243,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function GetCurrentHeadingId() {
|
function GetCurrentHeadingId() {
|
||||||
/* If we're at the bottom, force the last heading active (Learn-like behavior) */
|
/* If we're at the bottom, force the last heading active */
|
||||||
if (IsNearBottomOfPage()) {
|
if (IsNearBottomOfPage()) {
|
||||||
return headingElements[headingElements.length - 1].getAttribute('id');
|
return headingElements[headingElements.length - 1].getAttribute('id');
|
||||||
}
|
}
|
||||||
@@ -177,6 +277,11 @@
|
|||||||
function Update() {
|
function Update() {
|
||||||
ticking = false;
|
ticking = false;
|
||||||
|
|
||||||
|
/* If the viewport becomes narrow after load, avoid scroll fighting */
|
||||||
|
if (!IsDesktopViewport()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Date.now() < lockActiveUntilMs) {
|
if (Date.now() < lockActiveUntilMs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -187,7 +292,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeId = currentId;
|
activeId = currentId;
|
||||||
SetActiveTocLink(toc, activeId);
|
SetActiveTocLink(toc, activeId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OnScrollOrResize() {
|
function OnScrollOrResize() {
|
||||||
@@ -199,11 +304,7 @@
|
|||||||
window.requestAnimationFrame(Update);
|
window.requestAnimationFrame(Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('scroll', OnScrollOrResize, { passive: true });
|
function OnTocClick(evt) {
|
||||||
window.addEventListener('resize', OnScrollOrResize);
|
|
||||||
|
|
||||||
/* Update immediately and also when clicking TOC links */
|
|
||||||
toc.addEventListener('click', function (evt) {
|
|
||||||
var target = evt.target;
|
var target = evt.target;
|
||||||
if (!target || !target.classList || !target.classList.contains('page-toc__link')) {
|
if (!target || !target.classList || !target.classList.contains('page-toc__link')) {
|
||||||
return;
|
return;
|
||||||
@@ -223,29 +324,25 @@
|
|||||||
lockActiveUntilMs = Date.now() + 800;
|
lockActiveUntilMs = Date.now() + 800;
|
||||||
|
|
||||||
activeId = id;
|
activeId = id;
|
||||||
SetActiveTocLink(toc, activeId);
|
SetActiveTocLink(toc, activeId, true);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', OnScrollOrResize, { passive: true });
|
||||||
|
window.addEventListener('resize', OnScrollOrResize);
|
||||||
|
toc.addEventListener('click', OnTocClick);
|
||||||
|
|
||||||
Update();
|
Update();
|
||||||
|
|
||||||
|
return function DisposeScrollSpy() {
|
||||||
|
window.removeEventListener('scroll', OnScrollOrResize);
|
||||||
|
window.removeEventListener('resize', OnScrollOrResize);
|
||||||
|
toc.removeEventListener('click', OnTocClick);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function InitRightToc() {
|
function InitRightToc() {
|
||||||
if (!IsRightTocEnabled()) {
|
if (!IsRightTocEnabled()) {
|
||||||
return;
|
RemoveExistingToc();
|
||||||
}
|
|
||||||
|
|
||||||
/* Desktop-only TOC: on mobile it interferes with scrolling */
|
|
||||||
if (!IsDesktopViewport()) {
|
|
||||||
var existingWrap = document.querySelector('.main-content-wrap');
|
|
||||||
if (existingWrap) {
|
|
||||||
var existingToc = existingWrap.querySelector('.page-toc');
|
|
||||||
if (existingToc) {
|
|
||||||
existingToc.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
existingWrap.classList.remove('has-page-toc');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,31 +353,51 @@
|
|||||||
|
|
||||||
var headings = GetHeadings(main);
|
var headings = GetHeadings(main);
|
||||||
if (headings.length < 2) {
|
if (headings.length < 2) {
|
||||||
|
RemoveExistingToc();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsDesktopViewport()) {
|
||||||
|
RemoveExistingToc();
|
||||||
|
|
||||||
var wrap = document.querySelector('.main-content-wrap');
|
var wrap = document.querySelector('.main-content-wrap');
|
||||||
var content = document.querySelector('.main-content');
|
if (!wrap) {
|
||||||
if (!wrap || !content) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wrap.querySelector('.page-toc')) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap.classList.add('has-page-toc');
|
wrap.classList.add('has-page-toc');
|
||||||
|
|
||||||
var toc = BuildToc(headings);
|
var toc = BuildToc(headings, { variant: 'right' });
|
||||||
wrap.appendChild(toc);
|
wrap.appendChild(toc);
|
||||||
|
|
||||||
SetupScrollSpy(main, toc, headings);
|
scrollSpyDispose = SetupScrollSpy(main, toc, headings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Narrow viewports: place TOC at the top of the article (Learn-like) */
|
||||||
|
RemoveExistingToc();
|
||||||
|
|
||||||
|
var inlineToc = BuildToc(headings, { variant: 'inline', maxVisible: inlineMaxVisibleItems });
|
||||||
|
InsertInlineToc(main, inlineToc);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnViewportResize() {
|
||||||
|
if (null !== resizeReinitTimerId) {
|
||||||
|
window.clearTimeout(resizeReinitTimerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeReinitTimerId = window.setTimeout(InitRightToc, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', InitRightToc);
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
InitRightToc();
|
||||||
|
window.addEventListener('resize', OnViewportResize);
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitRightToc();
|
InitRightToc();
|
||||||
|
window.addEventListener('resize', OnViewportResize);
|
||||||
})();
|
})();
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Build
|
title: Build
|
||||||
nav_order: 9
|
nav_order: 10
|
||||||
prev_url: /drivers.html
|
prev_url: /drivers.html
|
||||||
prev_label: Drivers
|
prev_label: Drivers
|
||||||
next_url: /monitor.html
|
next_url: /monitor.html
|
||||||
@@ -620,7 +620,7 @@ During the build process, when **Build Deploy ISO** is enabled, the script creat
|
|||||||
|
|
||||||
You may want to disable Cleanup Deploy ISO in the following scenarios:
|
You may want to disable Cleanup Deploy ISO in the following scenarios:
|
||||||
|
|
||||||
* **Creating deployment media separately**: When you want to create USB deployment drives at a later time (e.g. using `.\FFUDevelopment\USBImagingToolCreator.ps1`)
|
* **Creating deployment media separately**: When you want to create USB deployment drives at a later time, see [USB Imaging Tool Creator](/FFU/usb_imaging_tool_creator.html) for a staged workflow using `USBImagingToolCreator.ps1` with a deploy ISO, `FFU`, and `Drivers` folder (local path or network share).
|
||||||
* **Testing in Hyper-V**: When deploying FFU images to Hyper-V VMs for testing, you can attach the deploy ISO directly to a VM as a DVD drive
|
* **Testing in Hyper-V**: When deploying FFU images to Hyper-V VMs for testing, you can attach the deploy ISO directly to a VM as a DVD drive
|
||||||
|
|
||||||
## Cleanup Drivers
|
## Cleanup Drivers
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
title: Create PE Media
|
||||||
|
nav_order: 1
|
||||||
|
prev_url: /helper_scripts.html
|
||||||
|
prev_label: Helper Scripts
|
||||||
|
next_url: /usb_imaging_tool_creator.html
|
||||||
|
next_label: USB Imaging Tool Creator
|
||||||
|
parent: Helper Scripts
|
||||||
|
---
|
||||||
|
# Create PE Media
|
||||||
|
|
||||||
|
`Create-PEMedia.ps1` is a standalone helper script that creates WinPE capture or deployment ISO files outside the main build flow.
|
||||||
|
|
||||||
|
This is useful when admins need to quickly generate a deploy ISO for a share (or local staging folder) that technicians will use with `USBImagingToolCreator.ps1`.
|
||||||
|
|
||||||
|
## Common use case
|
||||||
|
|
||||||
|
If your staging location does not already have a deployment ISO, run `Create-PEMedia.ps1` to generate one, then copy that ISO to the staging folder used by your technicians.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Run from an elevated PowerShell session.
|
||||||
|
- Windows ADK + WinPE add-on must be installed (default path: `C:\Program Files (x86)\Windows Kits\10\`).
|
||||||
|
- Script should be run from the `FFUDevelopment` folder (or provide explicit paths via parameters).
|
||||||
|
|
||||||
|
## Quick start (deploy ISO)
|
||||||
|
|
||||||
|
From `FFUDevelopment`, this creates a deploy ISO by default:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PEMedia.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Default output file:
|
||||||
|
|
||||||
|
- `.\WinPE_FFU_Deploy_x64.iso`
|
||||||
|
|
||||||
|
## Useful commands
|
||||||
|
|
||||||
|
Create deploy ISO for x64:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PEMedia.ps1 -Deploy $true -WindowsArch 'x64'
|
||||||
|
```
|
||||||
|
|
||||||
|
Create deploy ISO for ARM64:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PEMedia.ps1 -Deploy $true -WindowsArch 'arm64' -DeployISO "$PSScriptRoot\WinPE_FFU_Deploy_arm64.iso"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create capture ISO only:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PEMedia.ps1 -Capture $true -Deploy $false
|
||||||
|
```
|
||||||
|
|
||||||
|
Create deploy ISO and include PE drivers from `.\PEDrivers`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Create-PEMedia.ps1 -Deploy $true -CopyPEDrivers $true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stage output for USB imaging
|
||||||
|
|
||||||
|
After creating the deploy ISO, place it in the same staging root used for USB media creation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
\\Server\FFUStaging\
|
||||||
|
WinPE_FFU_Deploy_x64.iso
|
||||||
|
FFU\
|
||||||
|
<image files>.ffu
|
||||||
|
Drivers\
|
||||||
|
<optional driver content>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then technicians can run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\USBImagingToolCreator.ps1 -DeployISOPath "\\Server\FFUStaging\WinPE_FFU_Deploy_x64.iso" -DisableAutoPlay
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
`Create-PEMedia.ps1` writes log output to:
|
||||||
|
|
||||||
|
- `.\Create-PEMedia.log` (or custom path via `-LogFile`)
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
title: Helper Scripts
|
||||||
|
nav_order: 4
|
||||||
|
prev_url: /ui_overview.html
|
||||||
|
prev_label: UI Overview
|
||||||
|
next_url: /create_pemedia.html
|
||||||
|
next_label: Create PE Media
|
||||||
|
has_children: true
|
||||||
|
has_toc: false
|
||||||
|
---
|
||||||
|
# Helper Scripts
|
||||||
|
|
||||||
|
This section documents standalone helper scripts used outside the primary UI-driven build workflow.
|
||||||
|
|
||||||
|
## Available helper scripts
|
||||||
|
|
||||||
|
- [Create PE Media](/create_pemedia.html)
|
||||||
|
- [USB Imaging Tool Creator](/usb_imaging_tool_creator.html)
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 608 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: Monitor
|
||||||
|
nav_order: 11
|
||||||
|
prev_url: /build.html
|
||||||
|
prev_label: Build
|
||||||
|
parent: UI Overview
|
||||||
|
---
|
||||||
|
# Monitor
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The monitor tab parses the `.\FFUDevelopment\FFUDevelopment.log` file. This makes it easy to track what's happening during the FFU build process.
|
||||||
|
|
||||||
|
You can click into the monitor and select one or multiple lines (either single click, or shift+click). Doing so will prevent the log file from continuous scrolling, allowing you time to read what has transpired up to that point.
|
||||||
|
|
||||||
|
You can also copy your selection using ctrl+C. This makes it easy to copy and paste parts of the log file used for troubleshooting.
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
title: Parameters Reference
|
||||||
|
nav_order: 1
|
||||||
|
parent: Reference
|
||||||
|
prev_url: /reference.html
|
||||||
|
prev_label: Reference
|
||||||
|
---
|
||||||
|
# BuildFFUVM.ps1 Parameter Reference
|
||||||
|
|
||||||
|
This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.parameters-reference-table th:first-child,
|
||||||
|
.parameters-reference-table td:first-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
| Parameter | Type | UI Control | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| -AdditionalFFUFiles | string[] | Copy Additional FFU Files + Additional FFU Files list | Array of full file paths to existing FFU files that should also be copied to the deployment USB when -CopyAdditionalFFUFiles is set to $true. |
|
||||||
|
| -AllowExternalHardDiskMedia | bool | Allow External Hard Disk Media | When set to $true, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined. |
|
||||||
|
| -AllowVHDXCaching | bool | Allow VHDX Caching | When set to $true, will cache the VHDX file to the $FFUDevelopmentPath\VHDXCache folder and create a config json file that will keep track of the Windows build information, the updates installed, and the logical sector byte size information. Default is $false. |
|
||||||
|
| -AppListPath | string | AppList.json Path | Path to a JSON file containing a list of applications to install using WinGet. Default is $FFUDevelopmentPath\Apps\AppList.json. |
|
||||||
|
| -AppsScriptVariables | hashtable | Define Apps Script Variables + Apps Script Variables list | When passed a hashtable, the script will create an AppsScriptVariables.json file in the OrchestrationPath. This file will be used to pass variables to the Apps script. The hashtable should contain key-value pairs where the key is the variable name and the value is the variable value. |
|
||||||
|
| -BitsPriority | string | BITS Priority | BITS transfer priority used for downloads. Accepted values are 'Foreground', 'High', 'Normal', and 'Low'. Default is 'Normal'. |
|
||||||
|
| -BuildUSBDrive | bool | Build USB Drive | When set to $true, will partition and format a USB drive and copy the captured FFU to the drive. |
|
||||||
|
| -Cleanup | switch | Monitor cancel build action (no direct control) | Switch to run cleanup-only mode. When specified, the script performs cleanup and exits without starting a new build. |
|
||||||
|
| -CleanupAppsISO | bool | Cleanup Apps ISO | When set to $true, will remove the Apps ISO after the FFU has been captured. Default is $true. |
|
||||||
|
| -CleanupCaptureISO | bool | Cleanup Capture ISO | When set to $true, will remove the WinPE capture ISO after the FFU has been captured. Default is $true. |
|
||||||
|
| -CleanupCurrentRunDownloads | bool | Monitor cancel prompt option (no direct control) | When set to $true, cleanup mode will remove downloads created during the current run and restore backed up run JSON files. Default is $false. |
|
||||||
|
| -CleanupDeployISO | bool | Cleanup Deploy ISO | When set to $true, will remove the WinPE deployment ISO after the FFU has been captured. Default is $true. |
|
||||||
|
| -CleanupDrivers | bool | Cleanup Drivers | When set to $true, will remove the drivers folder after the FFU has been captured. Default is $true. |
|
||||||
|
| -CompactOS | bool | Compact OS | When set to $true, will compact the OS when building the FFU. Default is $true. |
|
||||||
|
| -CompressDownloadedDriversToWim | bool | Compress Driver Model Folder to WIM | When set to $true, compresses downloaded drivers into a WIM file. Default is $false. |
|
||||||
|
| -ConfigFile | string | Load Config File | Path to a JSON file containing parameters to use for the script. Default is $null. |
|
||||||
|
| -CopyAdditionalFFUFiles | bool | Copy Additional FFU Files | When set to $true, enables copying additional FFU files from $FFUDevelopmentPath\FFU to the deployment USB alongside the current build output. |
|
||||||
|
| -CopyAutopilot | bool | Copy Autopilot Profile | When set to $true, will copy the $FFUDevelopmentPath\Autopilot folder to the Deployment partition of the USB drive. Default is $false. |
|
||||||
|
| -CopyDrivers | bool | Copy Drivers to USB drive | When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers folder to the Drivers folder on the deploy partition of the USB drive. Default is $false. |
|
||||||
|
| -CopyPEDrivers | bool | Copy PE Drivers | When set to $true, enables adding WinPE drivers. By default copies drivers from $FFUDevelopmentPath\PEDrivers to the WinPE deployment media unless -UseDriversAsPEDrivers is also $true. |
|
||||||
|
| -CopyPPKG | bool | Copy Provisioning Package | When set to $true, will copy the provisioning package from the $FFUDevelopmentPath\PPKG folder to the Deployment partition of the USB drive. Default is $false. |
|
||||||
|
| -CopyUnattend | bool | Copy Unattend.xml | When set to $true, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Default is $false. |
|
||||||
|
| -CreateCaptureMedia | bool | Create Capture Media | When set to $true, this will create WinPE capture media for use when $InstallApps is set to $true. This capture media will be automatically attached to the VM, and the boot order will be changed to automate the capture of the FFU. |
|
||||||
|
| -CreateDeploymentMedia | bool | Create Deployment Media | When set to $true, this will create WinPE deployment media for use when deploying to a physical device. |
|
||||||
|
| -CustomFFUNameTemplate | string | Custom FFU Name Template | Sets a custom FFU output name with placeholders. Allowed placeholders are: {WindowsRelease}, {WindowsVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt}. |
|
||||||
|
| -Disksize | uint64 | Disk Size (GB) | Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk. |
|
||||||
|
| -DriversFolder | string | Drivers Folder | Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers. |
|
||||||
|
| -DriversJsonPath | string | Drivers.json Path | Path to a JSON file that specifies which drivers to download. |
|
||||||
|
| -ExportConfigFile | string | Save Config File | Path to a JSON file to export the parameters used for the script. |
|
||||||
|
| -FFUCaptureLocation | string | FFU Capture Location | Path to the folder where the captured FFU will be stored. Default is $FFUDevelopmentPath\FFU. |
|
||||||
|
| -FFUDevelopmentPath | string | FFU Development Path | Path to the FFU development folder. Default is $PSScriptRoot. |
|
||||||
|
| -FFUPrefix | string | VM Name Prefix | Prefix for the generated FFU file. Default is _FFU. |
|
||||||
|
| -Headers | hashtable | CLI only (no UI control) | Headers to use when downloading files. Not recommended to modify. |
|
||||||
|
| -InjectUnattend | bool | Inject Unattend.xml | When set to $true and InstallApps is also $true, copies unattend_[arch].xml from $FFUDevelopmentPath\unattend to $FFUDevelopmentPath\Apps\Unattend\Unattend.xml so sysprep can use it inside the VM. Default is $false. |
|
||||||
|
| -InstallApps | bool | Install Applications | When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created. |
|
||||||
|
| -InstallDrivers | bool | Install Drivers to FFU | Install device drivers from the specified $FFUDevelopmentPath\Drivers folder if set to $true. Download the drivers and put them in the Drivers folder. The script will recurse the drivers folder and add the drivers to the FFU. |
|
||||||
|
| -InstallOffice | bool | Install Office | Install Microsoft Office if set to $true. The script will download the latest ODT and Office files in the $FFUDevelopmentPath\Apps\Office folder and install Office in the FFU via VM. |
|
||||||
|
| -ISOPath | string | Windows ISO Path | Path to the Windows 10/11 ISO file. |
|
||||||
|
| -LogicalSectorSizeBytes | uint32 | Logical Sector Size | UInt32 value of 512 or 4096. Useful for 4Kn drives or devices shipping with UFS drives. Default is 512. |
|
||||||
|
| -Make | string | Make | Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell', 'HP', 'Lenovo'. |
|
||||||
|
| -MaxUSBDrives | int | Max USB Drives | Maximum number of USB drives to build in parallel. Default is 5. Set to 0 to process all discovered drives (or all selected drives when USBDriveList or selection is used). Actual throttle will never exceed the number of drives discovered. |
|
||||||
|
| -MediaType | string | Media Type | String value of either 'business' or 'consumer'. This is used to identify which media type to download. Default is 'consumer'. |
|
||||||
|
| -Memory | uint64 | Memory (GB) | Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Default is 4GB. |
|
||||||
|
| -Model | string | Driver Models list | Model of the device to download drivers. This is required if Make is set. |
|
||||||
|
| -OfficeConfigXMLFile | string | Office Configuration XML File | Path to a custom Office configuration XML file to use for installation. |
|
||||||
|
| -Optimize | bool | Optimize | When set to $true, will optimize the FFU file. Default is $true. |
|
||||||
|
| -OptionalFeatures | string | Optional Features | Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP). |
|
||||||
|
| -OrchestrationPath | string | Application Path (derived Orchestration path) | Path to the orchestration folder containing scripts that run inside the VM. Default is $FFUDevelopmentPath\Apps\Orchestration. |
|
||||||
|
| -PEDriversFolder | string | PE Drivers Folder | Path to the folder containing drivers to be injected into the WinPE deployment media. Default is $FFUDevelopmentPath\PEDrivers. |
|
||||||
|
| -Processors | int | Processors | Number of virtual processors for the virtual machine. Recommended to use at least 4. |
|
||||||
|
| -ProductKey | string | Product Key | Product key for the Windows edition specified in WindowsSKU. This will overwrite whatever SKU is entered for WindowsSKU. Recommended to use if you want to use a MAK or KMS key to activate Enterprise or Education. If using VL media instead of consumer media, you'll want to enter a MAK or KMS key here. |
|
||||||
|
| -PromptExternalHardDiskMedia | bool | Prompt for External Hard Disk Media | When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true. |
|
||||||
|
| -RemoveApps | bool | Remove Apps Folder Content | When set to $true, will remove the application content in the Apps folder after the FFU has been captured. Default is $true. |
|
||||||
|
| -RemoveFFU | bool | Remove FFU | When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false. |
|
||||||
|
| -RemoveUpdates | bool | Remove Downloaded Update Files | When set to $true, will remove the downloaded CU, MSRT, Defender, Edge, OneDrive, and .NET files downloaded. Default is $true. |
|
||||||
|
| -ShareName | string | Share Name | Name of the shared folder for FFU capture. The default is FFUCaptureShare. This share will be created with rights for the user account. When finished, the share will be removed. |
|
||||||
|
| -Threads | int | Threads | Controls the throttle applied to parallel tasks inside the script. Default is 5, matching the UI Threads field, and applies to driver downloads invoked through Invoke-ParallelProcessing. |
|
||||||
|
| -UpdateADK | bool | Update ADK | When set to $true, the script will check for and install the latest Windows ADK and WinPE add-on if they are not already installed or up-to-date. Default is $true. |
|
||||||
|
| -UpdateEdge | bool | Update Edge | When set to $true, will download and install the latest Microsoft Edge. Default is $false. |
|
||||||
|
| -UpdateLatestCU | bool | Update Latest Cumulative Update | When set to $true, will download and install the latest cumulative update. Default is $false. |
|
||||||
|
| -UpdateLatestDefender | bool | Update Defender | When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false. |
|
||||||
|
| -UpdateLatestMicrocode | bool | Update Latest Microcode (for LTSC/Server 2016/2019) | When set to $true, will download and install the latest microcode updates for applicable Windows releases (e.g., Windows Server 2016/2019, Windows 10 LTSC 2016/2019) into the FFU. Default is $false. |
|
||||||
|
| -UpdateLatestMSRT | bool | Update Microsoft Software Removal Tool (MSRT) | When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false. |
|
||||||
|
| -UpdateLatestNet | bool | Update .NET | When set to $true, will download and install the latest .NET Framework. Default is $false. |
|
||||||
|
| -UpdateOneDrive | bool | Update OneDrive (Per-Machine) | When set to $true, will download and install the latest OneDrive and install it as a per-machine installation instead of per-user. Default is $false. |
|
||||||
|
| -UpdatePreviewCU | bool | Update Preview Cumulative Update | When set to $true, will download and install the latest Preview cumulative update. Default is $false. |
|
||||||
|
| -USBDriveList | hashtable | USB Drives list | A hashtable containing USB drives from win32_diskdrive where:<br>- Key: USB drive model name (partial match supported)<br>- Value: USB drive UniqueId string, or an array of UniqueIds (to support selecting multiple drives with the same model)<br><br>Examples:<br>@{ "SanDisk Ultra" = "1234567890" }<br>@{ "SanDisk Ultra" = @("1234567890", "ABCDEFG"); "Kingston DataTraveler" = "0987654321" } |
|
||||||
|
| -UseDriversAsPEDrivers | bool | Use Drivers Folder as PE Drivers Source | When set to $true (and -CopyPEDrivers is also $true), bypasses the contents of $FFUDevelopmentPath\PEDrivers and instead builds the WinPE driver set dynamically from the $DriversFolder path, copying only the required WinPE drivers. Has no effect if -CopyPEDrivers is not specified. Default is $false. |
|
||||||
|
| -UserAgent | string | CLI only (no UI control) | User agent string to use when downloading files. |
|
||||||
|
| -UserAppListPath | string | Application Path (derived UserAppList.json) | Path to a JSON file containing a list of user-defined applications to install. Default is $FFUDevelopmentPath\Apps\UserAppList.json. |
|
||||||
|
| -Username | string | Username | Username for accessing the shared folder. The default is ffu_user. The script will auto-create the account and password. When finished, it will remove the account. |
|
||||||
|
| -VMHostIPAddress | string | VM Host IP Address | IP address of the Hyper-V host for FFU capture. If $InstallApps is set to $true, this parameter must be configured. You must manually configure this, or use the UI to auto-detect. |
|
||||||
|
| -VMLocation | string | VM Location | Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to. |
|
||||||
|
| -VMSwitchName | string | VM Switch Name + Custom VM Switch Name (when Other selected) | Name of the Hyper-V virtual switch. If $InstallApps is set to $true, this must be set to capture the FFU from the VM. |
|
||||||
|
| -WindowsArch | string | Windows Architecture | String value of 'x86', 'x64', or 'arm64'. This is used to identify which architecture of Windows to download. Default is 'x64'. |
|
||||||
|
| -WindowsLang | string | Windows Language | String value in language-region format (e.g., 'en-us'). This is used to identify which language of media to download. Default is 'en-us'. |
|
||||||
|
| -WindowsRelease | int | Windows Release | Integer value of 10, 11, 2016, 2019, 2021, 2022, 2024, or 2025. This is used to identify which Windows client/LTSC/server release to use. Default is 11. |
|
||||||
|
| -WindowsSKU | string | Windows SKU | Edition/SKU to install. Accepted values are: 'Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Enterprise 2016 LTSB', 'Enterprise N 2016 LTSB', 'Enterprise LTSC', 'Enterprise N LTSC', 'IoT Enterprise LTSC', 'IoT Enterprise N LTSC', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)'. |
|
||||||
|
| -WindowsVersion | string | Windows Version | String value of the Windows version to download. This is used to identify which version of Windows to download. Default is '25h2'. |
|
||||||
|
{: .parameters-reference-table }
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
@@ -25,6 +25,18 @@ After following this guide, you will have a USB drive with an FFU that contains
|
|||||||
* Drivers (Optional)
|
* Drivers (Optional)
|
||||||
* In some cases drivers aren't necessary and you can get away with Windows Update providing drivers. We'll go over how to add drivers via the UI for Microsoft, HP, Lenovo, or Dell devices
|
* In some cases drivers aren't necessary and you can get away with Windows Update providing drivers. We'll go over how to add drivers via the UI for Microsoft, HP, Lenovo, or Dell devices
|
||||||
|
|
||||||
|
## Video Walkthrough
|
||||||
|
|
||||||
|
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
|
||||||
|
<iframe
|
||||||
|
src="https://www.youtube-nocookie.com/embed/kOIK5OmDugc"
|
||||||
|
title="YouTube video player"
|
||||||
|
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Follow the [prerequisites](/FFU/prerequisites.html) documentation before getting started.
|
Follow the [prerequisites](/FFU/prerequisites.html) documentation before getting started.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: Reference
|
||||||
|
nav_order: 5
|
||||||
|
prev_url: /helper_scripts.html
|
||||||
|
prev_label: Helper Scripts
|
||||||
|
next_url: /parameters_reference.html
|
||||||
|
next_label: Parameters Reference
|
||||||
|
has_children: true
|
||||||
|
has_toc: false
|
||||||
|
---
|
||||||
|
# Reference
|
||||||
|
|
||||||
|
This section contains deep-dive documentation that explains how FFU Builder works behind the UI.
|
||||||
|
|
||||||
|
## Available reference guides
|
||||||
|
|
||||||
|
- [BuildFFUVM Parameter Reference](/FFU/parameters_reference.html)
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://rbalsleymsft.github.io/FFU/sitemap.xml
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
layout: null
|
||||||
|
---
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
{% assign sortedPages = site.pages | sort: "url" %}
|
||||||
|
{% for page in sortedPages %}
|
||||||
|
{% if page.sitemap != false and page.url != "/404.html" %}
|
||||||
|
{% if page.url == "/" or page.url contains ".html" %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ site.url }}{{ site.baseurl }}{{ page.url | replace: "index.html", "" }}</loc>
|
||||||
|
<lastmod>{{ site.time | date_to_xmlschema }}</lastmod>
|
||||||
|
</url>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</urlset>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
title: USB Imaging Tool Creator
|
||||||
|
nav_order: 2
|
||||||
|
prev_url: /create_pemedia.html
|
||||||
|
prev_label: Create PE Media
|
||||||
|
parent: Helper Scripts
|
||||||
|
---
|
||||||
|
# USB Imaging Tool Creator
|
||||||
|
|
||||||
|
`USBImagingToolCreator.ps1` is a standalone helper for creating one or more deployment USB drives from a deploy ISO, FFU files, and optional drivers. This is best used when you want to provide remote technicians the FFU file(s) you've built and optionally a drivers folder that contains the drivers for the models they will need to manage. They can also provide their own drivers (using Drivers\Make\Model format (e.g Drivers\Dell\Optiplex 7060 (085D))
|
||||||
|
|
||||||
|
## How the script works
|
||||||
|
|
||||||
|
- `-DeployISOPath` is required and should point to the deploy ISO file.
|
||||||
|
- The script uses the parent folder of that ISO as its working root.
|
||||||
|
- FFU files are copied from `<ISO parent>\FFU` (all `.ffu` files, recursive).
|
||||||
|
- Drivers are copied from `<ISO parent>\Drivers` (recursive) when present.
|
||||||
|
- If drivers are not found, the script creates an empty `Drivers` folder on each deploy partition.
|
||||||
|
- `-DisableAutoPlay` is optional and temporarily disables AutoPlay for the current user during media creation. This is useful in situations where you see File Explorer pop ups as it's building the USB drive.
|
||||||
|
|
||||||
|
## Network share workflow (admin/technician)
|
||||||
|
|
||||||
|
For a shared workflow, stage one folder on a share with the deploy ISO and content that technicians should copy to USB drives.
|
||||||
|
|
||||||
|
If you do not already have a deployment ISO in the staging location, create one first using [Create PE Media](/FFU/create_pemedia.html). This lets admins quickly generate the deploy ISO and then stage it for technicians using `USBImagingToolCreator.ps1`.
|
||||||
|
|
||||||
|
Example layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
\\Server\FFUStaging\
|
||||||
|
WinPE_FFU_Deploy.iso
|
||||||
|
FFU\
|
||||||
|
<image files>.ffu
|
||||||
|
Drivers\
|
||||||
|
<optional driver content>
|
||||||
|
```
|
||||||
|
|
||||||
|
Run from an elevated PowerShell session:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\USBImagingToolCreator.ps1 -DeployISOPath "\\Server\FFUStaging\WinPE_FFU_Deploy.iso" -DisableAutoPlay
|
||||||
|
```
|
||||||
|
|
||||||
|
The script passes `-DeployISOPath` directly to `Mount-DiskImage`, so use a path the local Windows host can mount.
|
||||||
|
|
||||||
|
## Example folder structure
|
||||||
|
|
||||||
|
In this example a folder named USBCreator was made and the Drivers and FFU folders as well as the WinPE_FFU_Deploy_x64.iso and USBImagingToolCreator.ps1 files were copied from the FFUDevelopment folder to this new folder.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## What happens when you run it
|
||||||
|
|
||||||
|
1. Detects disks with media type **Removable media** or **External hard disk media**.
|
||||||
|
2. Prompts for a single drive selection or an all-drives selection.
|
||||||
|
3. Stops `mmc` and `diskpart` processes to reduce drive lock issues.
|
||||||
|
4. Erases each selected disk and rebuilds it as MBR with:
|
||||||
|
- `Boot` partition (2 GB, FAT32, active)
|
||||||
|
- `Deploy` partition (remaining space, NTFS)
|
||||||
|
5. Mounts the deploy ISO and copies all ISO content to every `Boot` partition.
|
||||||
|
6. Copies FFU content to every `Deploy` partition.
|
||||||
|
7. Copies driver content into `Deploy\Drivers` (or creates an empty `Drivers` folder).
|
||||||
|
8. Dismounts the ISO and reports completion.
|
||||||
|
|
||||||
|
{: .warning-title}
|
||||||
|
|
||||||
|
> Warning
|
||||||
|
>
|
||||||
|
> Selected disks are fully erased (`Clear-Disk -RemoveData -RemoveOEM`), so verify drive selection carefully, especially when using the all-drives option.
|
||||||
|
|
||||||
|
## Logging and progress
|
||||||
|
|
||||||
|
- Progress is shown in the PowerShell progress UI.
|
||||||
|
- `Script.log` is written in the same folder as the deploy ISO (the working root folder).
|
||||||
|
|
||||||
|
{% include page_nav.html %}
|
||||||
Reference in New Issue
Block a user