mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
@@ -1,5 +1,34 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## **2408.1**
|
||||||
|
|
||||||
|
### External Drive Support
|
||||||
|
|
||||||
|
Up until now, the USB build process has supported using drives identified by Windows as removable drives. Most USB sticks will identify as removable, however faster drives may show up as external hard disk media. You may also have a smaller, portable SSD drive that you'd like to use for imaging since these are typically much faster than regular USB 3.x thumb drives.
|
||||||
|
|
||||||
|
In adding this support, I do realize that there is potential for data loss for those that might have external hard drives attached to their machines.
|
||||||
|
|
||||||
|
To handle this, with help from [HedgeComp](https://github.com/HedgeComp), we've refactored the `Get-USBDrives` function. Two new variables have been created:
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| AllowExternalHardDiskMedia | Bool | If `$true`, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined. |
|
||||||
|
| PromptExternalHardDiskMedia | Bool | If `$true` and AllowExternalHardDiskMedia is `$true`, the script will prompt to select which drive to use. When set to `$true`, only a single drive will be created. If `$false`, the script won't prompt for which external hard disk to use and can use multiple external hard disks, similar to how removable USB drives function. |
|
||||||
|
|
||||||
|
By default, this functionality won't effect previous USB drive creation behavior. However if you want to take advantage of the new functionality, set `-AllowExternalHardDiskMedia $true`
|
||||||
|
|
||||||
|
Fixes/misc
|
||||||
|
|
||||||
|
- Fixed a display issue where if multiple FFU files were in the FFU folder, the script wouldn't display which FFUs to choose from when running the script without -verbose. This will now display a table with the last modified date whether you run with the -verbose switch or not.
|
||||||
|
- Added start/end/duration time (thanks [HedgeComp](https://github.com/HedgeComp))
|
||||||
|
- Fixed an issue where deployment media wasn't prompting for a key to be pressed as expected
|
||||||
|
- Fixed an issue when creating the USB drive and the drive had a RAW partition style that clear-disk would generate an error
|
||||||
|
- Cleaned up some commented code
|
||||||
|
- Added Create-PEMedia.ps1 as a helper script to quickly generate Deploy or Capture media
|
||||||
|
- Fixed an issue with clean up of Defender/OneDrive/Edge
|
||||||
|
- Fixed an issue with the formatting of InstallAppsandSysprep.cmd file
|
||||||
|
- Updated parameter documentation in the script to include newly added parameters
|
||||||
|
|
||||||
## **2407.1**
|
## **2407.1**
|
||||||
|
|
||||||
This is another major release that includes:
|
This is another major release that includes:
|
||||||
|
|||||||
+304
-53
@@ -139,6 +139,30 @@ When set to $true, will remove the WinPE deployment ISO after the FFU has been c
|
|||||||
.PARAMETER CleanupAppsISO
|
.PARAMETER CleanupAppsISO
|
||||||
When set to $true, will remove the Apps ISO after the FFU has been captured. Default is $true.
|
When set to $true, will remove the Apps ISO after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
|
.PARAMETER DriversFolder
|
||||||
|
Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers.
|
||||||
|
|
||||||
|
.PARAMETER CleanupDrivers
|
||||||
|
When set to $true, will remove the drivers folder after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
|
.PARAMETER UserAgent
|
||||||
|
User agent string to use when downloading files.
|
||||||
|
|
||||||
|
.PARAMETER Headers
|
||||||
|
Headers to use when downloading files.
|
||||||
|
|
||||||
|
.PARAMETER AllowExternalHardDiskMedia
|
||||||
|
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.
|
||||||
|
|
||||||
|
.PARAMETER PromptExternalHardDiskMedia
|
||||||
|
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.
|
||||||
|
|
||||||
|
.PARAMETER Make
|
||||||
|
Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell', 'HP', 'Lenovo'
|
||||||
|
|
||||||
|
.PARAMETER Model
|
||||||
|
Model of the device to download drivers. This is required if Make is set.
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates.
|
Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates.
|
||||||
.\BuildFFUVM.ps1 -WindowsSKU 'Pro' -Installapps $true -InstallOffice $true -InstallDrivers $true -VMSwitchName 'Name of your VM Switch in Hyper-V' -VMHostIPAddress 'Your IP Address' -CreateCaptureMedia $true -CreateDeploymentMedia $true -BuildUSBDrive $true -UpdateLatestCU $true -UpdateLatestNet $true -UpdateLatestDefender $true -UpdateEdge $true -UpdateOneDrive $true -verbose
|
.\BuildFFUVM.ps1 -WindowsSKU 'Pro' -Installapps $true -InstallOffice $true -InstallDrivers $true -VMSwitchName 'Name of your VM Switch in Hyper-V' -VMHostIPAddress 'Your IP Address' -CreateCaptureMedia $true -CreateDeploymentMedia $true -BuildUSBDrive $true -UpdateLatestCU $true -UpdateLatestNet $true -UpdateLatestDefender $true -UpdateEdge $true -UpdateOneDrive $true -verbose
|
||||||
@@ -308,9 +332,11 @@ param(
|
|||||||
"Sec-Fetch-Site" = "none"
|
"Sec-Fetch-Site" = "none"
|
||||||
"Sec-Fetch-User" = "?1"
|
"Sec-Fetch-User" = "?1"
|
||||||
"Upgrade-Insecure-Requests" = "1"
|
"Upgrade-Insecure-Requests" = "1"
|
||||||
}
|
},
|
||||||
|
[bool]$AllowExternalHardDiskMedia,
|
||||||
|
[bool]$PromptExternalHardDiskMedia = $true
|
||||||
)
|
)
|
||||||
$version = '2407.1'
|
$version = '2408.1'
|
||||||
|
|
||||||
#Check if Hyper-V feature is installed (requires only checks the module)
|
#Check if Hyper-V feature is installed (requires only checks the module)
|
||||||
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
|
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
|
||||||
@@ -1635,8 +1661,8 @@ function Get-Office {
|
|||||||
$officeCommand = "d:\Office\setup.exe /configure d:\Office\DeployFFU.xml"
|
$officeCommand = "d:\Office\setup.exe /configure d:\Office\DeployFFU.xml"
|
||||||
|
|
||||||
# Check if Office command is not commented out or missing and fix it if it is
|
# Check if Office command is not commented out or missing and fix it if it is
|
||||||
if ($content[2] -ne $officeCommand) {
|
if ($content[3] -ne $officeCommand) {
|
||||||
$content[2] = $officeCommand
|
$content[3] = $officeCommand
|
||||||
|
|
||||||
# Write the modified content back to the file
|
# Write the modified content back to the file
|
||||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $content
|
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $content
|
||||||
@@ -2614,7 +2640,7 @@ function New-PEMedia {
|
|||||||
#Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null
|
#Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null
|
||||||
# $WinPEISOName = 'WinPE_FFU_Capture.iso'
|
# $WinPEISOName = 'WinPE_FFU_Capture.iso'
|
||||||
$WinPEISOFile = $CaptureISO
|
$WinPEISOFile = $CaptureISO
|
||||||
$Capture = $false
|
# $Capture = $false
|
||||||
}
|
}
|
||||||
If ($Deploy) {
|
If ($Deploy) {
|
||||||
WriteLog "Copying $FFUDevelopmentPath\WinPEDeployFFUFiles\* to WinPE deploy media"
|
WriteLog "Copying $FFUDevelopmentPath\WinPEDeployFFUFiles\* to WinPE deploy media"
|
||||||
@@ -2634,7 +2660,7 @@ function New-PEMedia {
|
|||||||
# $WinPEISOName = 'WinPE_FFU_Deploy.iso'
|
# $WinPEISOName = 'WinPE_FFU_Deploy.iso'
|
||||||
$WinPEISOFile = $DeployISO
|
$WinPEISOFile = $DeployISO
|
||||||
|
|
||||||
$Deploy = $false
|
# $Deploy = $false
|
||||||
}
|
}
|
||||||
WriteLog 'Dismounting WinPE media'
|
WriteLog 'Dismounting WinPE media'
|
||||||
Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -Save | Out-Null
|
Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -Save | Out-Null
|
||||||
@@ -2650,11 +2676,21 @@ function New-PEMedia {
|
|||||||
WriteLog "Creating WinPE ISO at $WinPEISOFile"
|
WriteLog "Creating WinPE ISO at $WinPEISOFile"
|
||||||
# & "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\$WinPEISOName | Out-null
|
# & "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\$WinPEISOName | Out-null
|
||||||
if($WindowsArch -eq 'x64'){
|
if($WindowsArch -eq 'x64'){
|
||||||
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
elseif($WindowsArch -eq 'arm64'){
|
elseif($WindowsArch -eq 'arm64'){
|
||||||
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Invoke-Process $OSCDIMG $OSCDIMGArgs
|
Invoke-Process $OSCDIMG $OSCDIMGArgs
|
||||||
WriteLog "ISO created successfully"
|
WriteLog "ISO created successfully"
|
||||||
@@ -2899,20 +2935,129 @@ 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
|
||||||
If ($USBDrives -and ($null -eq $USBDrives.count)) {
|
WriteLog 'Checking for USB drives'
|
||||||
$USBDrivesCount = 1
|
|
||||||
|
# Check if external hard disk media is allowed
|
||||||
|
If ($AllowExternalHardDiskMedia) {
|
||||||
|
# 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) {
|
||||||
|
# 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'
|
||||||
|
}
|
||||||
|
WriteLog 'Found external hard disk media drives'
|
||||||
|
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].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
|
||||||
|
}
|
||||||
|
$Output += New-Object PSObject -Property $Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# 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 {
|
||||||
|
# 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 {
|
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 {
|
||||||
@@ -2925,29 +3070,82 @@ 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 ($FFUFiles.Count -eq 1) {
|
# If there is exactly one FFU file, select it
|
||||||
|
if ($FFUCount -eq 1) {
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
$SelectedFFUFile = $FFUFiles.FullName
|
||||||
}
|
}
|
||||||
elseif ($FFUFiles.Count -gt 1) {
|
# If there are multiple FFU files, prompt the user to select one
|
||||||
WriteLog 'Found multiple FFU files'
|
elseif ($FFUCount -gt 1) {
|
||||||
for ($i = 0; $i -lt $FFUFiles.Count; $i++) {
|
WriteLog "Found $FFUCount FFU files"
|
||||||
WriteLog ("{0}: {1}" -f ($i + 1), $FFUFiles[$i].Name)
|
if($VerbosePreference -ne 'Continue'){
|
||||||
|
Write-Host "Found $FFUCount FFU files"
|
||||||
}
|
}
|
||||||
$inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all 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)
|
||||||
|
|
||||||
if ($inputChoice -eq 'A') {
|
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
|
||||||
}
|
|
||||||
elseif ($inputChoice -ge 1 -and $inputChoice -le $FFUFiles.Count) {
|
|
||||||
$selectedIndex = $inputChoice - 1
|
|
||||||
$SelectedFFUFile = $FFUFiles[$selectedIndex].FullName
|
|
||||||
}
|
|
||||||
WriteLog "$SelectedFFUFile was selected"
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
# Handle case where no FFU files are found
|
||||||
WriteLog "No FFU files found in the current directory."
|
WriteLog "No FFU files found in the current directory."
|
||||||
Write-Error "No FFU files found in the current directory."
|
Write-Error "No FFU files found in the current directory."
|
||||||
Return
|
Return
|
||||||
@@ -2963,10 +3161,22 @@ Function New-DeploymentUSB {
|
|||||||
|
|
||||||
$ScriptBlock = {
|
$ScriptBlock = {
|
||||||
param($DiskNumber)
|
param($DiskNumber)
|
||||||
Clear-Disk -Number $DiskNumber -RemoveData -RemoveOEM -Confirm:$false
|
|
||||||
Get-Disk $DiskNumber | Get-Partition | Remove-Partition
|
|
||||||
$Disk = Get-Disk -Number $DiskNumber
|
$Disk = Get-Disk -Number $DiskNumber
|
||||||
$Disk | Set-Disk -PartitionStyle MBR
|
# Clear-Disk -Number $DiskNumber -RemoveData -RemoveOEM -Confirm:$false
|
||||||
|
# Clear-disk has an unusual behavior where it sets external hard disk media as RAW, however removable media is set as MBR.
|
||||||
|
if ($Disk.PartitionStyle -ne "RAW") {
|
||||||
|
$Disk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false
|
||||||
|
$Disk = Get-Disk -Number $DiskNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
if($Disk.PartitionStyle -eq "RAW") {
|
||||||
|
$Disk | Initialize-Disk -PartitionStyle MBR -Confirm:$false
|
||||||
|
}
|
||||||
|
elseif($Disk.PartitionStyle -ne "RAW"){
|
||||||
|
$Disk | Get-Partition | Remove-Partition -Confirm:$false
|
||||||
|
$Disk | Set-Disk -PartitionStyle MBR
|
||||||
|
}
|
||||||
|
# Get-Disk $DiskNumber | Get-Partition | Remove-Partition
|
||||||
$BootPartition = $Disk | New-Partition -Size 2GB -IsActive -AssignDriveLetter
|
$BootPartition = $Disk | New-Partition -Size 2GB -IsActive -AssignDriveLetter
|
||||||
$DeployPartition = $Disk | New-Partition -UseMaximumSize -AssignDriveLetter
|
$DeployPartition = $Disk | New-Partition -UseMaximumSize -AssignDriveLetter
|
||||||
Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "TempBoot" -Confirm:$false
|
Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "TempBoot" -Confirm:$false
|
||||||
@@ -2977,7 +3187,8 @@ Function New-DeploymentUSB {
|
|||||||
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $DiskNumber | Out-null
|
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $DiskNumber | Out-null
|
||||||
WriteLog 'Done'
|
WriteLog 'Done'
|
||||||
|
|
||||||
$BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
# $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
||||||
|
$BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveLetter IS NOT NULL").Name
|
||||||
$ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\"
|
$ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\"
|
||||||
WriteLog "Copying WinPE files to $BootPartitionDriveLetter"
|
WriteLog "Copying WinPE files to $BootPartitionDriveLetter"
|
||||||
robocopy "$ISOMountPoint" "$BootPartitionDriveLetter" /E /COPYALL /R:5 /W:5 /J
|
robocopy "$ISOMountPoint" "$BootPartitionDriveLetter" /E /COPYALL /R:5 /W:5 /J
|
||||||
@@ -2985,7 +3196,8 @@ Function New-DeploymentUSB {
|
|||||||
|
|
||||||
if ($CopyFFU.IsPresent) {
|
if ($CopyFFU.IsPresent) {
|
||||||
if ($null -ne $SelectedFFUFile) {
|
if ($null -ne $SelectedFFUFile) {
|
||||||
$DeployPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempDeploy' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
# $DeployPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempDeploy' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
||||||
|
$DeployPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempDeploy' AND DriveLetter IS NOT NULL").Name
|
||||||
if ($SelectedFFUFile -is [array]) {
|
if ($SelectedFFUFile -is [array]) {
|
||||||
WriteLog "Copying multiple FFU files to $DeployPartitionDriveLetter. This could take a few minutes."
|
WriteLog "Copying multiple FFU files to $DeployPartitionDriveLetter. This could take a few minutes."
|
||||||
foreach ($FFUFile in $SelectedFFUFile) {
|
foreach ($FFUFile in $SelectedFFUFile) {
|
||||||
@@ -3143,34 +3355,34 @@ function Get-FFUEnvironment {
|
|||||||
#Clean up $KBPath
|
#Clean up $KBPath
|
||||||
If (Test-Path -Path $KBPath) {
|
If (Test-Path -Path $KBPath) {
|
||||||
WriteLog "Removing $KBPath"
|
WriteLog "Removing $KBPath"
|
||||||
Remove-Item -Path $KBPath -Recurse -Force
|
Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
WriteLog 'Removal complete'
|
WriteLog 'Removal complete'
|
||||||
}
|
}
|
||||||
#Clean up $DefenderPath
|
#Clean up $DefenderPath
|
||||||
If (Test-Path -Path $DefenderPath) {
|
If (Test-Path -Path $DefenderPath) {
|
||||||
WriteLog "Removing $DefenderPath"
|
WriteLog "Removing $DefenderPath"
|
||||||
Remove-Item -Path $DefenderPath -Recurse -Force
|
Remove-Item -Path $DefenderPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
WriteLog 'Removal complete'
|
WriteLog 'Removal complete'
|
||||||
}
|
}
|
||||||
#Clean up $OneDrivePath
|
#Clean up $OneDrivePath
|
||||||
If (Test-Path -Path $OneDrivePath) {
|
If (Test-Path -Path $OneDrivePath) {
|
||||||
WriteLog "Removing $OneDrivePath"
|
WriteLog "Removing $OneDrivePath"
|
||||||
Remove-Item -Path $OneDrivePath -Recurse -Force
|
Remove-Item -Path $OneDrivePath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
WriteLog 'Removal complete'
|
WriteLog 'Removal complete'
|
||||||
}
|
}
|
||||||
#Clean up $EdgePath
|
#Clean up $EdgePath
|
||||||
If (Test-Path -Path $EdgePath) {
|
If (Test-Path -Path $EdgePath) {
|
||||||
WriteLog "Removing $EdgePath"
|
WriteLog "Removing $EdgePath"
|
||||||
Remove-Item -Path $EdgePath -Recurse -Force
|
Remove-Item -Path $EdgePath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
WriteLog 'Removal complete'
|
WriteLog 'Removal complete'
|
||||||
}
|
}
|
||||||
if (Test-Path -Path "$AppsPath\Win32" -PathType Container) {
|
if (Test-Path -Path "$AppsPath\Win32" -PathType Container) {
|
||||||
WriteLog "Cleaning up Win32 folder"
|
WriteLog "Cleaning up Win32 folder"
|
||||||
Remove-Item -Path "$AppsPath\Win32" -Recurse -Force
|
Remove-Item -Path "$AppsPath\Win32" -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) {
|
if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) {
|
||||||
WriteLog "Cleaning up MSStore folder"
|
WriteLog "Cleaning up MSStore folder"
|
||||||
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force
|
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
Clear-InstallAppsandSysprep
|
Clear-InstallAppsandSysprep
|
||||||
Writelog 'Removing dirty.txt file'
|
Writelog 'Removing dirty.txt file'
|
||||||
@@ -3196,29 +3408,29 @@ function Clear-InstallAppsandSysprep {
|
|||||||
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Defender Platform Update"
|
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Defender Platform Update"
|
||||||
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
$CmdContent -notmatch 'd:\\Defender*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent -notmatch 'd:\\Defender*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
#Remove $DefenderPath
|
# #Remove $DefenderPath
|
||||||
WriteLog "Removing $DefenderPath"
|
# WriteLog "Removing $DefenderPath"
|
||||||
Remove-Item -Path $DefenderPath -Recurse -Force
|
# Remove-Item -Path $DefenderPath -Recurse -Force
|
||||||
WriteLog "Removal complete"
|
# WriteLog "Removal complete"
|
||||||
|
|
||||||
}
|
}
|
||||||
if ($UpdateOneDrive) {
|
if ($UpdateOneDrive) {
|
||||||
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove OneDrive install"
|
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove OneDrive install"
|
||||||
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
$CmdContent -notmatch 'd:\\OneDrive*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent -notmatch 'd:\\OneDrive*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
#Remove $OneDrivePath
|
# #Remove $OneDrivePath
|
||||||
WriteLog "Removing $OneDrivePath"
|
# WriteLog "Removing $OneDrivePath"
|
||||||
Remove-Item -Path $OneDrivePath -Recurse -Force
|
# Remove-Item -Path $OneDrivePath -Recurse -Force
|
||||||
WriteLog "Removal complete"
|
# WriteLog "Removal complete"
|
||||||
}
|
}
|
||||||
if ($UpdateEdge) {
|
if ($UpdateEdge) {
|
||||||
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Edge install"
|
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Edge install"
|
||||||
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
$CmdContent -notmatch 'd:\\Edge*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
$CmdContent -notmatch 'd:\\Edge*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
#Remove $EdgePath
|
# #Remove $EdgePath
|
||||||
WriteLog "Removing $EdgePath"
|
# WriteLog "Removing $EdgePath"
|
||||||
Remove-Item -Path $EdgePath -Recurse -Force
|
# Remove-Item -Path $EdgePath -Recurse -Force
|
||||||
WriteLog "Removal complete"
|
# WriteLog "Removal complete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3228,7 +3440,9 @@ function Clear-InstallAppsandSysprep {
|
|||||||
if (Test-Path -Path $Logfile) {
|
if (Test-Path -Path $Logfile) {
|
||||||
Remove-item -Path $LogFile -Force
|
Remove-item -Path $LogFile -Force
|
||||||
}
|
}
|
||||||
Write-Host "FFU build process has begun. This process can take 20 minutes or more. Please do not close this window or any additional windows that pop up"
|
$startTime = Get-Date
|
||||||
|
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"
|
Write-Host "To track progress, please open the log file $Logfile or use the -Verbose parameter next time"
|
||||||
|
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
@@ -3738,7 +3952,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
|
||||||
@@ -3749,6 +3963,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 {
|
||||||
@@ -3882,4 +4120,17 @@ Remove-Item -Path .\dirty.txt -Force | out-null
|
|||||||
if ($VerbosePreference -ne 'Continue'){
|
if ($VerbosePreference -ne 'Continue'){
|
||||||
Write-Host 'Script complete'
|
Write-Host 'Script complete'
|
||||||
}
|
}
|
||||||
WriteLog 'Script complete'
|
# Record the end time
|
||||||
|
$endTime = Get-Date
|
||||||
|
Write-Host "FFU build process completed at" $endTime
|
||||||
|
|
||||||
|
# Calculate the total run time
|
||||||
|
$runTime = $endTime - $startTime
|
||||||
|
|
||||||
|
# Format the runtime as minutes and seconds
|
||||||
|
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime
|
||||||
|
|
||||||
|
if ($VerbosePreference -ne 'Continue'){
|
||||||
|
Write-Host $runTimeFormatted
|
||||||
|
}
|
||||||
|
WriteLog 'Script complete: ' + $runTimeFormatted
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
param (
|
||||||
|
[string]$FFUDevelopmentPath = $PSScriptRoot,
|
||||||
|
[string]$adkPath = 'C:\Program Files (x86)\Windows Kits\10\',
|
||||||
|
[string]$WindowsArch = 'x64',
|
||||||
|
[bool]$CopyPEDrivers = $false,
|
||||||
|
[string]$CaptureISO = "$PSScriptRoot\WinPE_FFU_Capture_x64.iso",
|
||||||
|
[string]$DeployISO = "$PSScriptRoot\WinPE_FFU_Deploy_x64.iso",
|
||||||
|
[string]$LogFile = "$PSScriptRoot\Create-PEMedia.log",
|
||||||
|
[bool]$Capture,
|
||||||
|
[bool]$Deploy = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
function WriteLog($LogText) {
|
||||||
|
Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Verbose $LogText
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Process {
|
||||||
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
|
param
|
||||||
|
(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$FilePath,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string]$ArgumentList
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
|
||||||
|
$stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)"
|
||||||
|
|
||||||
|
$startProcessParams = @{
|
||||||
|
FilePath = $FilePath
|
||||||
|
ArgumentList = $ArgumentList
|
||||||
|
RedirectStandardError = $stdErrTempFile
|
||||||
|
RedirectStandardOutput = $stdOutTempFile
|
||||||
|
Wait = $true;
|
||||||
|
PassThru = $true;
|
||||||
|
NoNewWindow = $true;
|
||||||
|
}
|
||||||
|
if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) {
|
||||||
|
$cmd = Start-Process @startProcessParams
|
||||||
|
$cmdOutput = Get-Content -Path $stdOutTempFile -Raw
|
||||||
|
$cmdError = Get-Content -Path $stdErrTempFile -Raw
|
||||||
|
if ($cmd.ExitCode -ne 0) {
|
||||||
|
if ($cmdError) {
|
||||||
|
throw $cmdError.Trim()
|
||||||
|
}
|
||||||
|
if ($cmdOutput) {
|
||||||
|
throw $cmdOutput.Trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) {
|
||||||
|
WriteLog $cmdOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
#$PSCmdlet.ThrowTerminatingError($_)
|
||||||
|
WriteLog $_
|
||||||
|
Write-Host "Script failed - $Logfile for more info"
|
||||||
|
throw $_
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-PEMedia {
|
||||||
|
param (
|
||||||
|
[Parameter()]
|
||||||
|
[bool]$Capture,
|
||||||
|
[Parameter()]
|
||||||
|
[bool]$Deploy
|
||||||
|
)
|
||||||
|
#Need to use the Demployment and Imaging tools environment to create winPE media
|
||||||
|
$DandIEnv = "$adkPath`Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat"
|
||||||
|
$WinPEFFUPath = "$FFUDevelopmentPath\WinPE"
|
||||||
|
|
||||||
|
If (Test-path -Path "$WinPEFFUPath") {
|
||||||
|
WriteLog "Removing old WinPE path at $WinPEFFUPath"
|
||||||
|
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force | out-null
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLog "Copying WinPE files to $WinPEFFUPath"
|
||||||
|
if($WindowsArch -eq 'x64') {
|
||||||
|
& cmd /c """$DandIEnv"" && copype amd64 $WinPEFFUPath" | Out-Null
|
||||||
|
}
|
||||||
|
elseif($WindowsArch -eq 'arm64') {
|
||||||
|
& cmd /c """$DandIEnv"" && copype arm64 $WinPEFFUPath" | Out-Null
|
||||||
|
}
|
||||||
|
#Invoke-Process cmd "/c ""$DandIEnv"" && copype amd64 $WinPEFFUPath"
|
||||||
|
WriteLog 'Files copied successfully'
|
||||||
|
|
||||||
|
WriteLog 'Mounting WinPE media to add WinPE optional components'
|
||||||
|
Mount-WindowsImage -ImagePath "$WinPEFFUPath\media\sources\boot.wim" -Index 1 -Path "$WinPEFFUPath\mount" | Out-Null
|
||||||
|
WriteLog 'Mounting complete'
|
||||||
|
|
||||||
|
$Packages = @(
|
||||||
|
"WinPE-WMI.cab",
|
||||||
|
"en-us\WinPE-WMI_en-us.cab",
|
||||||
|
"WinPE-NetFX.cab",
|
||||||
|
"en-us\WinPE-NetFX_en-us.cab",
|
||||||
|
"WinPE-Scripting.cab",
|
||||||
|
"en-us\WinPE-Scripting_en-us.cab",
|
||||||
|
"WinPE-PowerShell.cab",
|
||||||
|
"en-us\WinPE-PowerShell_en-us.cab",
|
||||||
|
"WinPE-StorageWMI.cab",
|
||||||
|
"en-us\WinPE-StorageWMI_en-us.cab",
|
||||||
|
"WinPE-DismCmdlets.cab",
|
||||||
|
"en-us\WinPE-DismCmdlets_en-us.cab"
|
||||||
|
)
|
||||||
|
|
||||||
|
if($WindowsArch -eq 'x64'){
|
||||||
|
$PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\"
|
||||||
|
}
|
||||||
|
elseif($WindowsArch -eq 'arm64'){
|
||||||
|
$PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($Package in $Packages) {
|
||||||
|
$PackagePath = Join-Path $PackagePathBase $Package
|
||||||
|
WriteLog "Adding Package $Package"
|
||||||
|
Add-WindowsPackage -Path "$WinPEFFUPath\mount" -PackagePath $PackagePath | Out-Null
|
||||||
|
WriteLog "Adding package complete"
|
||||||
|
}
|
||||||
|
If ($Capture) {
|
||||||
|
WriteLog "Copying $FFUDevelopmentPath\WinPECaptureFFUFiles\* to WinPE capture media"
|
||||||
|
Copy-Item -Path "$FFUDevelopmentPath\WinPECaptureFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force | out-null
|
||||||
|
WriteLog "Copy complete"
|
||||||
|
#Remove Bootfix.bin - for BIOS systems, shouldn't be needed, but doesn't hurt to remove for our purposes
|
||||||
|
#Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null
|
||||||
|
# $WinPEISOName = 'WinPE_FFU_Capture.iso'
|
||||||
|
$WinPEISOFile = $CaptureISO
|
||||||
|
# $Capture = $false
|
||||||
|
}
|
||||||
|
If ($Deploy) {
|
||||||
|
WriteLog "Copying $FFUDevelopmentPath\WinPEDeployFFUFiles\* to WinPE deploy media"
|
||||||
|
Copy-Item -Path "$FFUDevelopmentPath\WinPEDeployFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force | Out-Null
|
||||||
|
WriteLog 'Copy complete'
|
||||||
|
#If $CopyPEDrivers = $true, add drivers to WinPE media using dism
|
||||||
|
if ($CopyPEDrivers) {
|
||||||
|
WriteLog "Adding drivers to WinPE media"
|
||||||
|
try {
|
||||||
|
Add-WindowsDriver -Path "$WinPEFFUPath\Mount" -Driver "$FFUDevelopmentPath\PEDrivers" -Recurse -ErrorAction SilentlyContinue | Out-null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog 'Some drivers failed to be added to the FFU. This can be expected. Continuing.'
|
||||||
|
}
|
||||||
|
WriteLog "Adding drivers complete"
|
||||||
|
}
|
||||||
|
# $WinPEISOName = 'WinPE_FFU_Deploy.iso'
|
||||||
|
$WinPEISOFile = $DeployISO
|
||||||
|
|
||||||
|
# $Deploy = $false
|
||||||
|
}
|
||||||
|
WriteLog 'Dismounting WinPE media'
|
||||||
|
Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -Save | Out-Null
|
||||||
|
WriteLog 'Dismount complete'
|
||||||
|
#Make ISO
|
||||||
|
if ($WindowsArch -eq 'x64') {
|
||||||
|
$OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg"
|
||||||
|
}
|
||||||
|
elseif ($WindowsArch -eq 'arm64') {
|
||||||
|
$OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\arm64\Oscdimg"
|
||||||
|
}
|
||||||
|
$OSCDIMG = "$OSCDIMGPath\oscdimg.exe"
|
||||||
|
WriteLog "Creating WinPE ISO at $WinPEISOFile"
|
||||||
|
# & "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\$WinPEISOName | Out-null
|
||||||
|
if($WindowsArch -eq 'x64'){
|
||||||
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif($WindowsArch -eq 'arm64'){
|
||||||
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Invoke-Process $OSCDIMG $OSCDIMGArgs
|
||||||
|
WriteLog "ISO created successfully"
|
||||||
|
WriteLog "Cleaning up $WinPEFFUPath"
|
||||||
|
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force
|
||||||
|
WriteLog 'Cleanup complete'
|
||||||
|
}
|
||||||
|
if($Capture){
|
||||||
|
New-PEMedia -Capture $Capture
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
New-PEMedia -Deploy $Deploy
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -14,10 +14,6 @@ function Get-USBDrive(){
|
|||||||
return $USBDriveLetter
|
return $USBDriveLetter
|
||||||
}
|
}
|
||||||
|
|
||||||
# function Get-HardDrive(){
|
|
||||||
# $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID
|
|
||||||
# return $DeviceID
|
|
||||||
# }
|
|
||||||
function Get-HardDrive(){
|
function Get-HardDrive(){
|
||||||
$SystemInfo = Get-WmiObject -Class 'Win32_ComputerSystem'
|
$SystemInfo = Get-WmiObject -Class 'Win32_ComputerSystem'
|
||||||
$Manufacturer = $SystemInfo.Manufacturer
|
$Manufacturer = $SystemInfo.Manufacturer
|
||||||
@@ -127,27 +123,12 @@ function Invoke-Process {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have
|
|
||||||
# a driver for the battery installed, so you'll need to inject drivers, which can be tricky because just injecting the battery driver might not be enough,
|
|
||||||
# you might also need other drivers that the battery driver is dependent on.
|
|
||||||
# function Get-Battery(){
|
|
||||||
# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35")
|
|
||||||
# {
|
|
||||||
# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..."
|
|
||||||
# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..."
|
|
||||||
# Start-Sleep 60
|
|
||||||
# }
|
|
||||||
|
|
||||||
# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU"
|
|
||||||
# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU"
|
|
||||||
# }
|
|
||||||
|
|
||||||
#Get USB Drive and create log file
|
#Get USB Drive and create log file
|
||||||
$LogFileName = 'ScriptLog.txt'
|
$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 = '2407.1'
|
$version = '2408.1'
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
WriteLog "Script version: $version"
|
WriteLog "Script version: $version"
|
||||||
|
|
||||||
@@ -432,10 +413,6 @@ If (Test-Path -Path $Drivers)
|
|||||||
Writelog 'No driver folders found'
|
Writelog 'No driver folders found'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script
|
|
||||||
#Get-Battery
|
|
||||||
|
|
||||||
#Partition drive
|
#Partition drive
|
||||||
Writelog 'Clean Disk'
|
Writelog 'Clean Disk'
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,19 +1,113 @@
|
|||||||
# 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. This video was taken with the 2407.2 build. Features released after that will not be demonstrated in the video.
|
||||||
|
|
||||||
|
[](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