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:
rbalsleyMSFT
2024-08-06 15:43:23 -07:00
parent 02d858f27f
commit e1ab74e5a3
2 changed files with 265 additions and 132 deletions
+201 -125
View File
@@ -2911,15 +2911,22 @@ Function Get-WindowsVersionInfo {
}
}
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'
# Check if external hard disk media is allowed
If ($AllowExternalHardDiskMedia) {
$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media' OR MediaType='External hard disk media'")
if ($PromptExternalHardDiskMedia){
# 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' }
# Get all removable and external hard disk media drives
[array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media' OR MediaType='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 ($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 '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'
@@ -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 '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++) {
$ExternalDiskNumber = $ExternalHardDiskDrives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "")
$ExternalDisk = Get-Disk -Number $ExternalDiskNumber
if ($VerbosePreference -ne 'Continue'){
# Write-Host ("{0}: {1}" -f ($i + 1), $ExternalHardDiskDrives[$i].Model)
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
$ExternalDiskNumber = $ExternalHardDiskDrives[$i].Index
$ExternalDisk = Get-Disk -Number $ExternalDiskNumber
$Index = $i + 1
$Name = $ExternalDisk.FriendlyName
$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
$ISnumber = [float]$inputChoice
# Format and display the output
$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
Write-Host "You selected Disk: $ISnumber"
$selectedIndex = $inputChoice - 1
break
# Prompt user to select a drive
do {
$inputChoice = Read-Host "Enter the number corresponding to the external hard disk media drive you want to use"
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 {
# If the input is not a valid number, display an error message
Write-Host "Invalid input. Please try again."
else {
# Handle invalid selection
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 ($ExternalDisk.OperationalStatus -eq 'Offline') {
if ($VerbosePreference -ne 'Continue') {
Write-Error "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
}
WriteLog "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
exit 1
}
}
if ($selectedIndex -ge 0 -and $selectedIndex -lt $ExternalHardDiskDrives.Count) {
#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 ($ExternalDisk.OperationalStatus -eq 'Offline') {
Write-Warning "Selected Drive is in an Offline State. Please check the drive status in Disk Manager and try again."
exit 1
}
else {
$USBDrives = $ExternalHardDiskDrives[$selectedIndex]
# Handle invalid input
if ($VerbosePreference -ne 'Continue') {
Write-Host "Invalid selection. Please try again."
}
WriteLog "Invalid selection. Please try again."
}
} else {
Write-Warning "Invalid selection. Exiting." | Out-Null
exit 1
}
} while ($null -eq $selectedIndex)
}
}
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 {
# Get only removable media drives
[array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'")
$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) {
WriteLog "No removable USB drive found. Exiting"
Write-Error "No removable USB drive found. Exiting"
exit 1
}
# Return the found USB drives and their count
return $USBDrives, $USBDrivesCount
}
Function New-DeploymentUSB {
@@ -3004,75 +3045,88 @@ Function New-DeploymentUSB {
WriteLog "BuildUSBPath is $BuildUSBPath"
$SelectedFFUFile = $null
# Check if the CopyFFU switch is present
if ($CopyFFU.IsPresent) {
# Get all FFU files in the specified directory
$FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu"
$FFUCount = $FFUFiles.count
if (-not $FFUFiles) {
# WriteLog "No FFU files found in the current directory."
Write-Error "No FFU files found in the current directory."
Return
}
elseif ($FFUFiles.Count -eq 1) {
$SelectedFFUFile = $FFUFiles.FullName
}
elseif ($FFUFiles.Count -gt 1) {
WriteLog 'Found multiple FFU files'
Write-Warning 'Found multiple FFU files'
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"
}
}
# 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'
# Loop until a valid FFU file is selected
do {
$inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files"
# 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)
}
else {
# Handle case where no FFU files are found
WriteLog "No FFU files found in the current directory."
Write-Error "No FFU files found in the current directory."
Return
}
}
$counter = 0
foreach ($USBDrive in $USBDrives) {
@@ -3363,7 +3417,7 @@ if (Test-Path -Path $Logfile) {
Remove-item -Path $LogFile -Force
}
$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 "To track progress, please open the log file $Logfile or use the -Verbose parameter next time"
@@ -3874,7 +3928,7 @@ Catch {
throw $_
}
#Clean up ffu_user and Share
#Clean up ffu_user and Share and clean up apps
If ($InstallApps) {
try {
Remove-FFUUserShare
@@ -3885,6 +3939,30 @@ If ($InstallApps) {
Remove-FFUVM -VMName $VMName
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
try {
@@ -4020,17 +4098,15 @@ if ($VerbosePreference -ne 'Continue'){
}
# Record the end time
$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
$runTime = $endTime - $startTime
$runTimeInMinutes = [math]::Round($runTime.TotalMinutes, 2)
# Format the runtime as minutes and seconds
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime
Write-Host $runTimeFormatted -ForegroundColor DarkGreen
WriteLog 'Script complete'
if ($VerbosePreference -ne 'Continue'){
Write-Host $runTimeFormatted
}
WriteLog 'Script complete: ' + $runTimeFormatted
+64 -7
View File
@@ -1,19 +1,76 @@
# 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
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
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
![image](https://github.com/rbalsleyMSFT/FFU/assets/53497092/5400a203-9c2e-42b2-b24c-ab8dfd922ba1)
The first 15 minutes of the following video includes a quick start demo to get started. Below the video are a list of chapters
[![Reimage Windows Fast with Full-Flash Update (FFU))](https://img.youtube.com/vi/rqXRbgeeKSQ/maxresdefault.jpg)](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