mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Refactored Get-USBDrives and New-DeploymentUSB to use tables when displaying multiple drives or FFUs to the user. Fixed an issue with cleaning up InstallAppsandSysprep.cmd. Re-wrote Readme.md.
This commit is contained in:
+195
-119
@@ -2911,15 +2911,22 @@ Function Get-WindowsVersionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Function Get-USBDrive {
|
Function Get-USBDrive {
|
||||||
# $USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'")
|
# Log the start of the USB drive check
|
||||||
WriteLog 'Checking for USB drives'
|
WriteLog 'Checking for USB drives'
|
||||||
|
|
||||||
|
# Check if external hard disk media is allowed
|
||||||
If ($AllowExternalHardDiskMedia) {
|
If ($AllowExternalHardDiskMedia) {
|
||||||
$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media' OR MediaType='External hard disk media'")
|
# Get all removable and external hard disk media drives
|
||||||
if ($PromptExternalHardDiskMedia){
|
[array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media' OR MediaType='External hard disk media'")
|
||||||
# List all drives with MediaType='External hard disk media' and have the end user pick which one to use
|
[array]$ExternalHardDiskDrives = $USBDrives | Where-Object { $_.MediaType -eq 'External hard disk media' }
|
||||||
[array]$ExternalHardDiskDrives = $USBDrives | Where-Object { $_.MediaType -eq 'External hard disk media' }
|
$ExternalCount = $ExternalHardDiskDrives.Count
|
||||||
|
$USBDrivesCount = $USBDrives.Count
|
||||||
|
|
||||||
|
# Check if user should be prompted for external hard disk media
|
||||||
|
if ($PromptExternalHardDiskMedia) {
|
||||||
if ($ExternalHardDiskDrives) {
|
if ($ExternalHardDiskDrives) {
|
||||||
if ($VerbosePreference -ne 'Continue'){
|
# Log and warn about found external hard disk media drives
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
Write-Warning 'Found external hard disk media drives'
|
Write-Warning 'Found external hard disk media drives'
|
||||||
Write-Warning 'Will prompt for user input to select the drive to use to prevent accidental data loss'
|
Write-Warning 'Will prompt for user input to select the drive to use to prevent accidental data loss'
|
||||||
Write-Warning 'If you do not want to be prompted for this in the future, set -PromptExternalHardDiskMedia to $false'
|
Write-Warning 'If you do not want to be prompted for this in the future, set -PromptExternalHardDiskMedia to $false'
|
||||||
@@ -2928,71 +2935,105 @@ Function Get-USBDrive {
|
|||||||
WriteLog 'Will prompt for user input to select the drive to use to prevent accidental data loss'
|
WriteLog 'Will prompt for user input to select the drive to use to prevent accidental data loss'
|
||||||
WriteLog 'If you do not want to be prompted for this in the future, set -PromptExternalHardDiskMedia to $false'
|
WriteLog 'If you do not want to be prompted for this in the future, set -PromptExternalHardDiskMedia to $false'
|
||||||
|
|
||||||
|
# Prepare output for user selection
|
||||||
|
$Output = @()
|
||||||
for ($i = 0; $i -lt $ExternalHardDiskDrives.Count; $i++) {
|
for ($i = 0; $i -lt $ExternalHardDiskDrives.Count; $i++) {
|
||||||
$ExternalDiskNumber = $ExternalHardDiskDrives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "")
|
$ExternalDiskNumber = $ExternalHardDiskDrives[$i].Index
|
||||||
$ExternalDisk = Get-Disk -Number $ExternalDiskNumber
|
$ExternalDisk = Get-Disk -Number $ExternalDiskNumber
|
||||||
if ($VerbosePreference -ne 'Continue'){
|
$Index = $i + 1
|
||||||
# Write-Host ("{0}: {1}" -f ($i + 1), $ExternalHardDiskDrives[$i].Model)
|
$Name = $ExternalDisk.FriendlyName
|
||||||
Write-Host ("Drive {0}: {1} SN/{2} PartitionStyle={3} Status={4}" -f ($i + 1), $ExternalDisk.FriendlyName , $ExternalHardDiskDrives[$i].serialnumber, $ExternalDisk.PartitionStyle,$ExternalDisk.OperationalStatus ) -ForegroundColor Green
|
$SerialNumber = $ExternalHardDiskDrives[$i].serialnumber
|
||||||
|
$PartitionStyle = $ExternalDisk.PartitionStyle
|
||||||
|
$Status = $ExternalDisk.OperationalStatus
|
||||||
|
$Properties = [ordered]@{
|
||||||
|
'Drive Number' = $Index
|
||||||
|
'Drive Name' = $Name
|
||||||
|
'Serial Number' = $SerialNumber
|
||||||
|
'Partition Style' = $PartitionStyle
|
||||||
|
'Status' = $Status
|
||||||
}
|
}
|
||||||
WriteLog ("Drive {0}: {1} SN/{2} PartitionStyle={3} Status={4}" -f ($i + 1), $ExternalDisk.FriendlyName , $ExternalHardDiskDrives[$i].serialnumber, $ExternalDisk.PartitionStyle,$ExternalDisk.OperationalStatus )
|
$Output += New-Object PSObject -Property $Properties
|
||||||
}
|
}
|
||||||
while ($true) {
|
|
||||||
try {
|
|
||||||
# Ask the user for input
|
|
||||||
#$userInput = Read-Host "Please enter a number"
|
|
||||||
$inputChoice = $(Write-Host "Enter the number corresponding to the external hard disk media drive you want to use: " -ForegroundColor DarkYellow -NoNewline; Read-Host)
|
|
||||||
|
|
||||||
# Convert the input to a float
|
# Format and display the output
|
||||||
$ISnumber = [float]$inputChoice
|
$FormattedOutput = $Output | Format-Table -AutoSize -Property 'Drive Number', 'Drive Name', 'Serial Number', 'Partition Style', 'Status' | Out-String
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
$FormattedOutput | Out-Host
|
||||||
|
}
|
||||||
|
WriteLog $FormattedOutput
|
||||||
|
|
||||||
# Display the entered number used for Debugging
|
# Prompt user to select a drive
|
||||||
Write-Host "You selected Disk: $ISnumber"
|
do {
|
||||||
$selectedIndex = $inputChoice - 1
|
$inputChoice = Read-Host "Enter the number corresponding to the external hard disk media drive you want to use"
|
||||||
break
|
if ($inputChoice -match '^\d+$') {
|
||||||
|
$inputChoice = [int]$inputChoice
|
||||||
|
if ($inputChoice -ge 1 -and $inputChoice -le $ExternalCount) {
|
||||||
|
$SelectedIndex = $inputChoice - 1
|
||||||
|
$ExternalDiskNumber = $ExternalHardDiskDrives[$SelectedIndex].Index
|
||||||
|
$ExternalDisk = Get-Disk -Number $ExternalDiskNumber
|
||||||
|
$USBDrives = $ExternalHardDiskDrives[$SelectedIndex]
|
||||||
|
$USBDrivesCount = $USBDrives.Count
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Drive $inputChoice was selected"
|
||||||
|
}
|
||||||
|
WriteLog "Drive $inputChoice was selected"
|
||||||
}
|
}
|
||||||
catch {
|
else {
|
||||||
# If the input is not a valid number, display an error message
|
# Handle invalid selection
|
||||||
Write-Host "Invalid input. Please try again."
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
WriteLog "Invalid selection. Please try again."
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
# Check if the selected drive is offline
|
||||||
if ($selectedIndex -ge 0 -and $selectedIndex -lt $ExternalHardDiskDrives.Count) {
|
if ($ExternalDisk.OperationalStatus -eq 'Offline') {
|
||||||
#Check if Selected Drive is in an Offline State. Useful when presenting the FFU Driv to Hyper-V VMs and forget to Online Again
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
if ($ExternalDisk.OperationalStatus -eq 'Offline') {
|
Write-Error "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
|
||||||
Write-Warning "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
|
}
|
||||||
exit 1
|
WriteLog "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$USBDrives = $ExternalHardDiskDrives[$selectedIndex]
|
# Handle invalid input
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
WriteLog "Invalid selection. Please try again."
|
||||||
}
|
}
|
||||||
|
} while ($null -eq $selectedIndex)
|
||||||
} else {
|
}
|
||||||
Write-Warning "Invalid selection. Exiting." | Out-Null
|
}
|
||||||
exit 1
|
else {
|
||||||
}
|
# Log the count of found USB drives
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Found $USBDrivesCount total USB drives"
|
||||||
|
If ($ExternalCount -gt 0) {
|
||||||
|
Write-Host "$ExternalCount are external drives"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteLog "Found $USBDrivesCount total USB drives"
|
||||||
|
If ($ExternalCount -gt 0) {
|
||||||
|
WriteLog "$ExternalCount are external drives"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'")
|
|
||||||
}
|
|
||||||
If ($USBDrives -and ($null -eq $USBDrives.count)) {
|
|
||||||
$USBDrivesCount = 1
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
# Get only removable media drives
|
||||||
|
[array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'")
|
||||||
$USBDrivesCount = $USBDrives.Count
|
$USBDrivesCount = $USBDrives.Count
|
||||||
|
WriteLog "Found $USBDrivesCount Removable USB drives"
|
||||||
}
|
}
|
||||||
WriteLog "Found $USBDrivesCount USB drives"
|
|
||||||
|
|
||||||
|
# Check if any USB drives were found
|
||||||
if ($null -eq $USBDrives) {
|
if ($null -eq $USBDrives) {
|
||||||
WriteLog "No removable USB drive found. Exiting"
|
WriteLog "No removable USB drive found. Exiting"
|
||||||
Write-Error "No removable USB drive found. Exiting"
|
Write-Error "No removable USB drive found. Exiting"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Return the found USB drives and their count
|
||||||
return $USBDrives, $USBDrivesCount
|
return $USBDrives, $USBDrivesCount
|
||||||
}
|
}
|
||||||
Function New-DeploymentUSB {
|
Function New-DeploymentUSB {
|
||||||
@@ -3005,73 +3046,86 @@ Function New-DeploymentUSB {
|
|||||||
|
|
||||||
$SelectedFFUFile = $null
|
$SelectedFFUFile = $null
|
||||||
|
|
||||||
|
# Check if the CopyFFU switch is present
|
||||||
if ($CopyFFU.IsPresent) {
|
if ($CopyFFU.IsPresent) {
|
||||||
|
# Get all FFU files in the specified directory
|
||||||
$FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu"
|
$FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu"
|
||||||
|
$FFUCount = $FFUFiles.count
|
||||||
|
|
||||||
|
# If there is exactly one FFU file, select it
|
||||||
|
if ($FFUCount -eq 1) {
|
||||||
|
$SelectedFFUFile = $FFUFiles.FullName
|
||||||
|
}
|
||||||
|
# If there are multiple FFU files, prompt the user to select one
|
||||||
|
elseif ($FFUCount -gt 1) {
|
||||||
|
WriteLog "Found $FFUCount FFU files"
|
||||||
|
if($VerbosePreference -ne 'Continue'){
|
||||||
|
Write-Host "Found $FFUCount FFU files"
|
||||||
|
}
|
||||||
|
$output = @()
|
||||||
|
# Create a table of FFU files with their index, name, and last modified date
|
||||||
|
for ($i = 0; $i -lt $FFUCount; $i++) {
|
||||||
|
$index = $i + 1
|
||||||
|
$name = $FFUFiles[$i].Name
|
||||||
|
$modified = $FFUFiles[$i].LastWriteTime
|
||||||
|
$Properties = [ordered]@{
|
||||||
|
'FFU Number' = $index
|
||||||
|
'FFU Name' = $name
|
||||||
|
'Last Modified' = $modified
|
||||||
|
}
|
||||||
|
$output += New-Object PSObject -Property $Properties
|
||||||
|
}
|
||||||
|
$output | Format-Table -AutoSize -Property 'FFU Number', 'FFU Name', 'Last Modified'
|
||||||
|
|
||||||
if (-not $FFUFiles) {
|
# Loop until a valid FFU file is selected
|
||||||
# WriteLog "No FFU files found in the current directory."
|
do {
|
||||||
Write-Error "No FFU files found in the current directory."
|
$inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files"
|
||||||
Return
|
# Check if the input is a valid number or 'A'
|
||||||
}
|
if ($inputChoice -match '^\d+$' -or $inputChoice -eq 'A') {
|
||||||
|
if ($inputChoice -eq 'A') {
|
||||||
|
# Select all FFU files
|
||||||
|
$SelectedFFUFile = $FFUFiles.FullName
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host 'Will copy all FFU files'
|
||||||
|
}
|
||||||
|
WriteLog 'Will copy all FFU Files'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Convert input to integer and validate the selection
|
||||||
|
$inputChoice = [int]$inputChoice
|
||||||
|
if ($inputChoice -ge 1 -and $inputChoice -le $FFUCount) {
|
||||||
|
$selectedIndex = $inputChoice - 1
|
||||||
|
$SelectedFFUFile = $FFUFiles[$selectedIndex].FullName
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "$SelectedFFUFile was selected"
|
||||||
|
}
|
||||||
|
WriteLog "$SelectedFFUFile was selected"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Handle invalid selection
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
WriteLog "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Handle invalid input
|
||||||
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
|
Write-Host "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
WriteLog "Invalid selection. Please try again."
|
||||||
|
}
|
||||||
|
} while ($null -eq $SelectedFFUFile)
|
||||||
|
|
||||||
elseif ($FFUFiles.Count -eq 1) {
|
}
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
else {
|
||||||
}
|
# Handle case where no FFU files are found
|
||||||
elseif ($FFUFiles.Count -gt 1) {
|
WriteLog "No FFU files found in the current directory."
|
||||||
WriteLog 'Found multiple FFU files'
|
Write-Error "No FFU files found in the current directory."
|
||||||
Write-Warning 'Found multiple FFU files'
|
Return
|
||||||
for ($i = 0; $i -lt $FFUFiles.Count; $i++) {
|
}
|
||||||
WriteLog ("FFU {0}: {1} Modified: {2}" -f ($i + 1), $FFUFiles[$i].Name, $FFUFiles[$i].LastWriteTime)
|
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host ("FFU {0}: {1} Modified: {2}" -f ($i + 1), $FFUFiles[$i].Name, $FFUFiles[$i].LastWriteTime) -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#$inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files"
|
|
||||||
$inputChoice = $(Write-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files: " -ForegroundColor DarkYellow -NoNewline; Read-Host)
|
|
||||||
|
|
||||||
Write-Host "You selected FFU: $inputChoice"
|
|
||||||
WriteLog "You selected FFU: $inputChoice"
|
|
||||||
|
|
||||||
while ($true) {
|
|
||||||
#If 'A' is selected copy all the FFUs found
|
|
||||||
if ($inputChoice -eq 'A') {
|
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
|
||||||
Write-Host "You selected $inputChoice"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
# Try to Convert the inputChoice to a float
|
|
||||||
$ISnumber = [float]$inputChoice
|
|
||||||
|
|
||||||
# Display the entered number for debuggin
|
|
||||||
#Write-Host "You selected Disk: $ISnumber"
|
|
||||||
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
# If inputChoice is not a valid number, must have been a character, so display an error message
|
|
||||||
Write-Host "Invalid input. Please try again."
|
|
||||||
|
|
||||||
}
|
|
||||||
# If InputChoice is a number check that its withing the range of
|
|
||||||
if ($inputChoice -ge 1 -and $inputChoice -le $FFUFiles.Count) {
|
|
||||||
$selectedIndex = $inputChoice - 1
|
|
||||||
Write-Host "You Selected FFU $selectedIndex"
|
|
||||||
$SelectedFFUFile = $FFUFiles[$selectedIndex].FullName
|
|
||||||
break
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
#No correct input for FFU selection, so prompt again and repeat Checks.
|
|
||||||
Write-Host "Invalid FFU Number. Please try again."
|
|
||||||
$inputChoice = $(Write-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files: " -ForegroundColor DarkYellow -NoNewline; Read-Host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WriteLog "$SelectedFFUFile was selected"
|
|
||||||
Write-Host "$SelectedFFUFile was selected"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$counter = 0
|
$counter = 0
|
||||||
|
|
||||||
@@ -3363,7 +3417,7 @@ if (Test-Path -Path $Logfile) {
|
|||||||
Remove-item -Path $LogFile -Force
|
Remove-item -Path $LogFile -Force
|
||||||
}
|
}
|
||||||
$startTime = Get-Date
|
$startTime = Get-Date
|
||||||
Write-Host "FFU build process has begun at" $startTime -ForegroundColor DarkYellow
|
Write-Host "FFU build process started at" $startTime
|
||||||
Write-Host "This process can take 20 minutes or more. Please do not close this window or any additional windows that pop up"
|
Write-Host "This process can take 20 minutes or more. Please do not close this window or any additional windows that pop up"
|
||||||
Write-Host "To track progress, please open the log file $Logfile or use the -Verbose parameter next time"
|
Write-Host "To track progress, please open the log file $Logfile or use the -Verbose parameter next time"
|
||||||
|
|
||||||
@@ -3874,7 +3928,7 @@ Catch {
|
|||||||
throw $_
|
throw $_
|
||||||
|
|
||||||
}
|
}
|
||||||
#Clean up ffu_user and Share
|
#Clean up ffu_user and Share and clean up apps
|
||||||
If ($InstallApps) {
|
If ($InstallApps) {
|
||||||
try {
|
try {
|
||||||
Remove-FFUUserShare
|
Remove-FFUUserShare
|
||||||
@@ -3885,6 +3939,30 @@ If ($InstallApps) {
|
|||||||
Remove-FFUVM -VMName $VMName
|
Remove-FFUVM -VMName $VMName
|
||||||
throw $_
|
throw $_
|
||||||
}
|
}
|
||||||
|
#Clean up InstallAppsandSysprep.cmd
|
||||||
|
try {
|
||||||
|
WriteLog "Cleaning up $AppsPath\InstallAppsandSysprep.cmd"
|
||||||
|
Clear-InstallAppsandSysprep
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host 'Cleaning up InstallAppsandSysprep.cmd failed'
|
||||||
|
Writelog "Cleaning up InstallAppsandSysprep.cmd failed with error $_"
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (Test-Path -Path "$AppsPath\Win32" -PathType Container) {
|
||||||
|
WriteLog "Cleaning up Win32 folder"
|
||||||
|
Remove-Item -Path "$AppsPath\Win32" -Recurse -Force
|
||||||
|
}
|
||||||
|
if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) {
|
||||||
|
WriteLog "Cleaning up MSStore folder"
|
||||||
|
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "$_"
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#Clean up VM or VHDX
|
#Clean up VM or VHDX
|
||||||
try {
|
try {
|
||||||
@@ -4020,17 +4098,15 @@ if ($VerbosePreference -ne 'Continue'){
|
|||||||
}
|
}
|
||||||
# Record the end time
|
# Record the end time
|
||||||
$endTime = Get-Date
|
$endTime = Get-Date
|
||||||
Write-Host "FFU build process has completed at" $endTime -ForegroundColor DarkYellow
|
Write-Host "FFU build process completed at" $endTime
|
||||||
|
|
||||||
# Calculate the total run time
|
# Calculate the total run time
|
||||||
$runTime = $endTime - $startTime
|
$runTime = $endTime - $startTime
|
||||||
$runTimeInMinutes = [math]::Round($runTime.TotalMinutes, 2)
|
|
||||||
|
|
||||||
# Format the runtime as minutes and seconds
|
# Format the runtime as minutes and seconds
|
||||||
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime
|
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime
|
||||||
|
|
||||||
Write-Host $runTimeFormatted -ForegroundColor DarkGreen
|
if ($VerbosePreference -ne 'Continue'){
|
||||||
|
Write-Host $runTimeFormatted
|
||||||
|
}
|
||||||
|
WriteLog 'Script complete: ' + $runTimeFormatted
|
||||||
WriteLog 'Script complete'
|
|
||||||
|
|||||||
@@ -1,19 +1,76 @@
|
|||||||
# Using Full Flash Update (FFU) files to speed up Windows deployment
|
# Using Full Flash Update (FFU) files to speed up Windows deployment
|
||||||
|
|
||||||
This repo contains the full FFU process that we use in US Education at Microsoft to help customers with large deployments of Windows as they prepare for the new school year. This process isn't limited to only large deployments at the start of the year, but is the most common.
|
What if you could have a Windows image that has:
|
||||||
|
|
||||||
This process will copy Windows in about 2-3 minutes to the target device, optionally copy drivers, provisioning packages, Autopilot, etc. School technicians have even given the USB sticks to teachers and teachers calling them their "Magic USB sticks" to quickly get student devices reimaged in the event of an issue with their Windows PC.
|
- The latest Windows cumulative update
|
||||||
|
- The latest .NET cumulative update
|
||||||
|
- The latest Windows Defender Platform and Definition Updates
|
||||||
|
- The latest version of Microsoft Edge
|
||||||
|
- The latest version of OneDrive (Per-Machine)
|
||||||
|
- The latest version of Microsoft 365 Apps/Office
|
||||||
|
- The latest drivers from any of the major OEMs (Dell, HP, Lenovo, Microsoft) (yes, the latest, not some out of date enterprise CAB file from years ago)
|
||||||
|
- Winget support so you can integrate any app available from Winget directly in your image
|
||||||
|
- ARM64 support for the latest Copilot+ PCs
|
||||||
|
- The ability to bring your own drivers and apps if necessary
|
||||||
|
- Custom WinRE support
|
||||||
|
|
||||||
While we use this in Education at Microsoft, other industries can use it as well. We esepcially see a need for something like this with partners who do re-imaging on behalf of customers. The difference in Education is that they typically have large deployments that tend to happen at the beginning of the school year and any amount of time saved is helpful. Microsoft Deployment Toolkit, Configuration Manager, and other community solutions are all great solutions, but are typically slower due to WIM deployments being file-based while FFU files are sector-based.
|
And the best part: it takes less than two minutes to apply the image, even with all of these updates added to the media. After setting Windows up and going through Autopilot or a provisioning package, total elapsed time ~10 minutes (depending on what Intune or your device management tool is deploying).
|
||||||
|
|
||||||
|
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
|
# Updates
|
||||||
|
|
||||||
2407.1 has been released! Check out the changes in the new [Change Log](ChangeLog.md)
|
2408.1 has been released! Check out the changes in the [Change Log](ChangeLog.md)
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
If you're not familiar with Github, you can click the Green code button above and select download zip. Extract the zip file and make sure to copy the FFUDevelopment folder to the root of your C: drive. That will make it easy to follow the guide and allow the scripts to work properly.
|
- Download the latest [release](https://github.com/rbalsleyMSFT/FFU/releases)
|
||||||
|
- Extract the FFUDevelopment folder from the ZIP file (recommend to C:\FFUDevelopment)
|
||||||
|
- Follow the doc: C:\FFUDevelopment\Docs\BuildDeployFFU.docx
|
||||||
|
|
||||||
If extracted correctly, your c:\FFUDevelopment folder should look like the following. If it does, go to c:\FFUDevelopment\Docs\BuildDeployFFU.docx to get started.
|
## YouTube Detailed Walkthrough
|
||||||
|
|
||||||

|
The first 15 minutes of the following video includes a quick start demo to get started. Below the video are a list of chapters
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=rqXRbgeeKSQ "Reimage Windows Fast with Full-Flash Update (FFU))")
|
||||||
|
|
||||||
|
Chapters:
|
||||||
|
|
||||||
|
[00:00](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=0s) Begin
|
||||||
|
[03:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=201s) Quick Start Prereqs
|
||||||
|
[07:19](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=439s) Quick Start Demo
|
||||||
|
[14:12](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=852s) Script Parameters
|
||||||
|
[17:22](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1042s) Obtaining Windows Media
|
||||||
|
[25:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1555s) Adding Applications
|
||||||
|
[26:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1619s) Adding M365 Apps/Office
|
||||||
|
[29:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1761s) Adding Applications via Winget
|
||||||
|
[34:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2099s) Bring your own Applications
|
||||||
|
[36:01](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2161s) Customizing InstallAppsAndSysprep.cmd
|
||||||
|
[38:34](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2314s) Demo - Application Configuration
|
||||||
|
[49:43](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2983s) Drivers
|
||||||
|
[55:39](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3339s) Automatically downloading drivers
|
||||||
|
[57:28](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3448s) Microsoft Surface drivers
|
||||||
|
[58:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3535s) Dell drivers
|
||||||
|
[01:01:45](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3705s) Lenovo drivers
|
||||||
|
[01:03:16](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3796s) HP drivers
|
||||||
|
[01:05:25](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3925s) Bring your own drivers
|
||||||
|
[01:06:24](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3984s) Demo - Drivers
|
||||||
|
[01:11:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4315s) Multi-model driver support
|
||||||
|
[01:13:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4401s) Device naming
|
||||||
|
[01:18:30](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4710s) Device enrollment
|
||||||
|
[01:21:43](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4903s) Autopilot
|
||||||
|
[01:24:57](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5097s) Provisioning packages
|
||||||
|
[01:26:54](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5214s) Custom WinRE
|
||||||
|
[01:29:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5399s) Demo - Putting it all together (Deep dive)
|
||||||
|
[01:32:06](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5526s) Downloading Lenovo 500w drivers
|
||||||
|
[01:33:28](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5608s) Downloading apps via Winget
|
||||||
|
[01:36:54](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5814s) Downloading Office, Defender, Edge, OneDrive
|
||||||
|
[01:38:15](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5895s) Building the Apps.iso
|
||||||
|
[01:39:08](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5948s) Applying Windows to the VHDX
|
||||||
|
[01:40:16](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6016s) Downloading and applying cumulative updates
|
||||||
|
[01:41:44](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6104s) Building the VM
|
||||||
|
[01:48:13](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6493s) Capturing the FFU
|
||||||
|
[01:53:38](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6818s) Creating USB drive
|
||||||
|
[01:58:41](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=7121s) Deploying FFU
|
||||||
|
[02:11:48](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=7908s) Troubleshooting
|
||||||
|
[02:14:30](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=8070s) EDU Endpoint Office Hours
|
||||||
|
|||||||
Reference in New Issue
Block a user