removed old FFUDev folder

This commit is contained in:
rbalsleyMSFT
2023-05-22 17:03:30 -07:00
parent 526148b306
commit c0ef99d8e4
24 changed files with 0 additions and 2035 deletions
@@ -1,13 +0,0 @@
REM Put each app install on a separate line
REM M365 Apps/Office ProPlus
d:\Office\setup.exe /configure d:\office\DeployFFU.xml
REM Add additional apps below here
REM The below lines will remove the unattend.xml that gets the machine into audit mode. If not removed, the OS will get stuck booting to audit mode each time.
REM Also kills the sysprep process in order to automate sysprep generalize
del c:\windows\panther\unattend\unattend.xml /F /Q
del c:\windows\panther\unattend.xml /F /Q
taskkill /IM sysprep.exe
timeout /t 5
c:\windows\system32\sysprep\sysprep.exe /quiet /generalize /oobe
@@ -1,17 +0,0 @@
<Configuration ID="efa6df21-a106-428e-8eaa-d89a5dda6030">
<Add OfficeClientEdition="64" Channel="MonthlyEnterprise">
<Product ID="O365ProPlusRetail">
<Language ID="MatchOS" />
<ExcludeApp ID="Access" />
<ExcludeApp ID="Lync" />
<ExcludeApp ID="Publisher" />
<ExcludeApp ID="Bing" />
</Product>
</Add>
<Property Name="SharedComputerLicensing" Value="0" />
<Property Name="FORCEAPPSHUTDOWN" Value="FALSE" />
<Property Name="DeviceBasedLicensing" Value="0" />
<Property Name="SCLCacheOverride" Value="0" />
<Updates Enabled="TRUE" />
<Display Level="None" AcceptEULA="TRUE" />
</Configuration>
@@ -1,17 +0,0 @@
<Configuration ID="efa6df21-a106-428e-8eaa-d89a5dda6030">
<Add SourcePath = "C:\FFUDevelopment\Apps\Office" OfficeClientEdition="64" Channel="MonthlyEnterprise">
<Product ID="O365ProPlusRetail">
<Language ID="MatchOS" />
<ExcludeApp ID="Access" />
<ExcludeApp ID="Lync" />
<ExcludeApp ID="Publisher" />
<ExcludeApp ID="Bing" />
</Product>
</Add>
<Property Name="SharedComputerLicensing" Value="0" />
<Property Name="FORCEAPPSHUTDOWN" Value="FALSE" />
<Property Name="DeviceBasedLicensing" Value="0" />
<Property Name="SCLCacheOverride" Value="0" />
<Updates Enabled="TRUE" />
<Display Level="None" AcceptEULA="TRUE" />
</Configuration>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="auditUser">
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RunAsynchronous>
<RunAsynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>d:\InstallAppsandSysprep.cmd</Path>
</RunAsynchronousCommand>
</RunAsynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Reseal>
<Mode>Audit</Mode>
</Reseal>
</component>
</settings>
<cpi:offlineImage cpi:source="wim:c:/wimtoffu/win11_22h2_feb2023_consumer.wim#Windows 11 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>
-60
View File
@@ -1,60 +0,0 @@
#Modify variables
$VMPath = "c:\VM\$VMName"
$ISOPath = "E:\software\ISOs\Windows\Windows 11\en-us_windows_11_consumer_editions_version_22h2_updated_feb_2023_x64_dvd_4fa87138.iso"
$memory = 8GB
$disksize = 30GB
$processors = 4
$rand = get-random
$VMName = "_FFU-$rand"
$VHDPath = "$VMPath\$VMName.vhdx"
# 0. Delete old VMs and remove old certs
$certPath = 'Cert:\LocalMachine\Shielded VM Local Certificates\'
$vms = get-vm _ffu-* | ? {$_.state -ne 'running'}
If($null -ne $vms){
Foreach ($vm in $vms){
$OldVMName = $vm.VMName
Remove-VM -Name $vm.name -Force -ErrorAction SilentlyContinue
Remove-Item -Path "C:\VM\$OldVMName" -Force -Recurse -ErrorAction SilentlyContinue
Remove-HgsGuardian -Name $OldVMName
$certs = Get-ChildItem -Path $certPath -Recurse | Where-Object { $_.Subject -like "*$OldVMName*" }
foreach ($cert in $Certs){
Remove-item -Path $cert.PSPath -force
}
}
}
# 1. Create Dynamic Hard Disk
mkdir -Path $VMPath -Force
New-VHD -Path $VHDPath -Fixed -SizeBytes $disksize
#New-VHD -path $VHDPath -SizeBytes 128000000000 -LogicalSectorSizeBytes 512 -Dynamic
# 2. Create VM
# - Name: _FFU
# - Location: C:\VM\_FFU
# - Generation: 2
# - Memory: 4GB static
# - Networking: Not connected
# - Connect to existing VHD: c:\VM\_FFU\
# - Mount ISO
$VM = New-VM -Name $VMName -Path $VMPath -MemoryStartupBytes $memory -VHDPath $VHDPath -Generation 2
Set-VMProcessor -VMName $VMName -Count $processors
Add-VMDvdDrive -VMName $VMName -Path $ISOPath
$VMDVDDrive = Get-VMDvdDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive
#Configure TPM
New-HgsGuardian -Name $VMName -GenerateCertificates
$owner = get-hgsguardian -Name $VMName
$kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot
Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData
Enable-VMTPM -VMName $VMName
#Connect to VM
vmconnect $VM.ComputerName $VMName
#Use if creating for MDT/SCCM Builds
#Get-VM $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "Intel(R) I350 Gigabit Network Connection - Virtual Switch"
#$networkAdapter = Get-VMNetworkAdapter -VMName $VMName
-563
View File
@@ -1,563 +0,0 @@
#Requires -Modules Hyper-V, Storage
#Requires -PSEdition Desktop
# To do list (will get around to this stuff sometime next week)
# Change from using variables to parameters - this way you don't even need to open the script to edit it.
# Clean up the output it sends to powershell so you know what step you're on
# Do some real logging incase folks get in trouble and need my help
# Edit the WinPECaptureFFUFiles\CaptureFFU.ps1 file within this script instead of doing it manually. 
# Make Capture/Deploy media creation optional
# Change DownloadFFU.xml file to match C:\FFUDevelopment path
# If drivers = true, check if drivers folder exists
# C:\VM\VMFolder isn't being deleted
#Modify Required variables
$ISOPath = "E:\software\ISOs\Windows\Windows 11\en-us_windows_11_consumer_editions_version_22h2_updated_feb_2023_x64_dvd_4fa87138.iso"
$WindowsSKU = 'Pro'
$FFUDevelopmentPath = 'C:\FFUDevelopment'
$AppsISO = "$FFUDevelopmentPath\Apps.iso"
$AppsPath = "$FFUDevelopmentPath\Apps"
$InstallOffice = $true
$InstallApps = $true
$InstallDrivers = $true
$memory = 8GB
$disksize = 30GB
$processors = 4
$VMSwitchName = '*intel*'
#Optional variables
$rand = get-random
$VMName = "_FFU-$rand"
$VMLocation = "c:\VM"
$VMPath = $VMLocation + $VMName
$VHDXPath = "$VMPath\$VMName.vhdx"
#FUNCTIONS
Function Get-ADK {
# Define the registry key and value name to query
$adkRegKey = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots"
$adkRegValueName = "KitsRoot10"
# Check if the registry key exists
if (Test-Path $adkRegKey) {
# Get the registry value for the Windows ADK installation path
$adkPath = (Get-ItemProperty -Path $adkRegKey -Name $adkRegValueName).$adkRegValueName
if ($adkPath) {
return $adkPath
}
}
else {
throw "Windows ADK is not installed or the installation path could not be found."
}
}
function Get-ODTURL {
[String]$MSWebPage = Invoke-RestMethod 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117'
$MSWebPage | ForEach-Object {
if ($_ -match 'url=(https://.*officedeploymenttool.*\.exe)') {
$matches[1]
}
}
}
function Get-Office {
#Download ODT
$ODTUrl = Get-ODTURL
$ODTInstallFile = "$env:TEMP\odtsetup.exe"
Invoke-WebRequest -Uri $ODTUrl -OutFile $ODTInstallFile
# Extract ODT
$ODTPath = "$AppsPath\Office"
Start-Process -FilePath $ODTInstallFile -ArgumentList "/extract:$ODTPath /quiet" -Wait
# Run setup.exe with config.xml
$ConfigXml = "$ODTPath\DownloadFFU.xml"
#Set-Location $ODTPath
Start-Process -FilePath "$ODTPath\setup.exe" -ArgumentList "/download $ConfigXml" -Wait
#Clean up default configuration files
Remove-Item -Path "$ODTPath\configuration*" -Force
}
function New-AppsISO {
#Create Apps ISO file
$OSCDIMG = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe'
Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m -d $Appspath $AppsISO" -wait
#Remove the Office Download and ODT
if ($InstallOffice) {
$ODTPath = "$AppsPath\Office"
$OfficeDownloadPath = "$ODTPath\Office"
Remove-Item -Path $OfficeDownloadPath -Recurse -Force
Remove-Item -Path "$ODTPath\setup.exe"
}
}
function Get-WimFromISO {
# Mount the ISO file using Mount-DiskImage cmdlet
$mountResult = Mount-DiskImage -ImagePath $isoPath -PassThru
# Get the drive letter of the mounted ISO
$driveLetter = ($mountResult | Get-Volume).DriveLetter
# Construct the path to the install.wim file
$wimPath = $driveLetter + ":\sources\install.wim"
# Display the path to the install.wim file
Write-Host "The path to the install.wim file is: $wimPath"
return $wimpath
}
function Get-WimIndex {
[Parameter(Mandatory = $true)]
[string]$WindowsSKU
$wimindex = switch ($WindowsSKU) {
Home { 1 }
Home_N { 2 }
Home_SL { 3 }
EDU { 4 }
EDU_N { 5 }
Pro { 6 }
Pro_N { 7 }
Pro_EDU { 8 }
Pro_Edu_N { 9 }
Pro_WKS { 10 }
Pro_WKS_N { 11 }
Default { 6 }
}
Return $WimIndex
}
#Build VHDX
#Create VHDX
function New-ScratchVhdx {
param(
[Parameter(Mandatory = $true)]
[string]$VhdxPath,
[uint64]$SizeBytes = 30GB,
[ValidateSet(512, 4096)]
[uint32]$LogicalSectorSizeBytes = 512,
[switch]$Dynamic,
[Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]$PartitionStyle = [Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]::GPT
)
Write-Host "Creating new Scratch VHDX..."
$newVHDX = New-VHD -Path $VhdxPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes -Fixed:$true
$toReturn = $newVHDX | Mount-VHD -Passthru | Initialize-Disk -PassThru -PartitionStyle GPT
#Remove auto-created system partition so we can create the correct partition layout
remove-partition $toreturn.DiskNumber -PartitionNumber 1 -Confirm:$False
Write-Host "Done."
return $toReturn
}
#Add System Partition
function New-SystemPartition {
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[uint64]$SystemPartitionSize = 256MB
)
Write-Host "Creating System partition..."
$sysPartition = $VhdxDisk | New-Partition -AssignDriveLetter -Size $SystemPartitionSize -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" -IsHidden
$sysPartition | Format-Volume -FileSystem FAT32 -Force -NewFileSystemLabel "System"
Write-Host "Done. System partition at drive $($sysPartition.DriveLetter):"
return $sysPartition.DriveLetter
}
#Add MSRPartition - skip this initially unless needed
function New-MSRPartition {
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk
)
Write-Host "Creating MSR partition..."
$toReturn = $VhdxDisk | New-Partition -AssignDriveLetter -Size 16MB -GptType "{e3c9e316-0b5c-4db8-817d-f92df00215ae}" -IsHidden
Write-Host "Done."
return $toReturn
}
#Add OS Partition
function New-OSPartition {
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[Parameter(Mandatory = $true)]
[string]$WimPath,
[uint32]$WimIndex,
[uint64]$OSPartitionSize = 0
)
Write-Host "Creating OS partition..."
if ($OSPartitionSize -gt 0) {
$osPartition = $vhdxDisk | New-Partition -AssignDriveLetter -Size $OSPartitionSize
}
else {
$osPartition = $vhdxDisk | New-Partition -AssignDriveLetter -UseMaximumSize
}
$osPartition | Format-Volume -FileSystem NTFS -Confirm:$false -Force -NewFileSystemLabel "Windows"
Write-Host "Done. OS partition at drive $($osPartition.DriveLetter):"
Write-Host "Writing WIM at $WimPath to OS partition at drive $($osPartition.DriveLetter):..."
#Server 2019 is missing the Windows Overlay Filter (wof.sys), likely other Server SKUs are missing it as well. Script will error if trying to use the -compact switch on Server OSes
if ((Get-CimInstance Win32_OperatingSystem).Caption -match "Server") {
Write-Host (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\")
}
else {
Write-Host (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\" -Compact)
}
Write-Host "Done."
return $osPartition
}
#Add Recovery partition
function New-RecoveryPartition {
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[Parameter(Mandatory = $true)]
$OsPartition,
[uint64]$RecoveryPartitionSize = 0,
[ciminstance]$DataPartition
)
Write-Host "Creating empty Recovery partition (to be filled on first boot automatically)..."
$calculatedRecoverySize = 0
$recoveryPartition = $null
if ($RecoveryPartitionSize -gt 0) {
$calculatedRecoverySize = $RecoveryPartitionSize
}
else {
$winReWim = Get-ChildItem "$($OsPartition.DriveLetter):\Windows\System32\Recovery\Winre.wim"
if (($null -ne $winReWim) -and ($winReWim.Count -eq 1)) {
# Wim size + 52MB is minimum WinRE partition size.
# NTFS and other partitioning size differences account for about 17MB of space that's unavailable.
# Adding 32MB as a buffer to ensure there's enough space.
$calculatedRecoverySize = $winReWim.Length + 52MB + 32MB
Write-Host "Calculated space needed for recovery in bytes: $calculatedRecoverySize"
if ($null -ne $DataPartition) {
$DataPartition | Resize-Partition -Size ($DataPartition.Size - $calculatedRecoverySize)
Write-Host "Data partition shrunk by $calculatedRecoverySize bytes for Recovery partition."
}
else {
$OsPartition | Resize-Partition -Size ($OsPartition.Size - $calculatedRecoverySize)
Write-Host "OS partition shrunk by $calculatedRecoverySize bytes for Recovery partition."
}
$recoveryPartition = $VhdxDisk | New-Partition -AssignDriveLetter -UseMaximumSize -GptType "{de94bba4-06d1-4d40-a16a-bfd50179d6ac}" `
| Format-Volume -FileSystem NTFS -Confirm:$false -Force -NewFileSystemLabel "WinRE"
Write-Host "Done. Recovery partition at drive $($recoveryPartition.DriveLetter):"
}
else {
Write-Host "No WinRE.WIM found in the OS partition under \Windows\System32\Recovery."
Write-Host "Skipping creating the Recovery partition."
Write-Host "If a Recovery partition is desired, please re-run the script setting the -RecoveryPartitionSize flag as appropriate."
}
}
return $recoveryPartition
}
#Add boot files
function Add-BootFiles {
param(
[Parameter(Mandatory = $true)]
[string]$OsPartitionDriveLetter,
[Parameter(Mandatory = $true)]
[string]$SystemPartitionDriveLetter,
[string]$FirmwareType = 'UEFI'
)
Write-Host "Adding boot files for `"$($OsPartitionDriveLetter):\Windows`" to System partition `"$($SystemPartitionDriveLetter):`"..."
bcdboot "$($OsPartitionDriveLetter):\Windows" /S "$($SystemPartitionDriveLetter):" /F "$FirmwareType"
Write-Host "Done."
}
#Dismount VHDX
function Dismount-ScratchVhdx {
param(
[Parameter(Mandatory = $true)]
[string]$VhdxPath
)
if (Test-Path $VhdxPath) {
Write-Host "Dismounting scratch VHDX..."
Dismount-VHD -Path $VhdxPath
Write-Host "Done."
}
}
#Delete old VMs and remove old certs
function Remove-FFUVM {
$certPath = 'Cert:\LocalMachine\Shielded VM Local Certificates\'
$OLDFFUVMs = get-vm _ffu-* | Where-Object { $_.state -ne 'running' }
If ($null -ne $OLDFFUVMs) {
Foreach ($OLDFFUVM in $OLDFFUVMs) {
$OldVMName = $OLDFFUVM.VMName
Remove-VM -Name $OLDFFUVM.name -Force -ErrorAction SilentlyContinue
#Remove-Item -Path "C:\VM\$OldVMName" -Force -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path "$VMLocation\$OLDVMName" -Force -Recurse -ErrorAction SilentlyContinue
Remove-HgsGuardian -Name $OldVMName
$certs = Get-ChildItem -Path $certPath -Recurse | Where-Object { $_.Subject -like "*$OldVMName*" }
foreach ($cert in $Certs) {
Remove-item -Path $cert.PSPath -force
}
}
}
}
function New-FFUVM {
#Create new Gen2 VM
$VM = New-VM -Name $VMName -Path $VMPath -MemoryStartupBytes $memory -VHDPath $VHDXPath -Generation 2
Set-VMProcessor -VMName $VMName -Count $processors
#Mount Office ISO
Add-VMDvdDrive -VMName $VMName -Path $AppsISO
$VMHardDiskDrive = Get-VMHarddiskdrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMHardDiskDrive
Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -StaticMemory
#Configure TPM
New-HgsGuardian -Name $VMName -GenerateCertificates
$owner = get-hgsguardian -Name $VMName
$kp = New-HgsKeyProtector -Owner $owner -AllowUntrustedRoot
Set-VMKeyProtector -VMName $VMName -KeyProtector $kp.RawData
Enable-VMTPM -VMName $VMName
#Connect to VM
vmconnect $VM.ComputerName $VMName
#Start VM
Start-VM -Name $VMName
return $VM
}
function New-PEMedia {
param (
[Parameter()]
[switch]
$Capture,
[Parameter()]
[switch]
$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") {
#Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -discard
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force
}
& cmd /c """$DandIEnv"" && copype amd64 $WinPEFFUPath"
Mount-WindowsImage -ImagePath "$WinPEFFUPath\media\sources\boot.wim" -Index 1 -Path "$WinPEFFUPath\mount"
$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"
)
$PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\"
foreach ($Package in $Packages) {
$PackagePath = Join-Path $PackagePathBase $Package
Add-WindowsPackage -Path "$WinPEFFUPath\mount" -PackagePath $PackagePath | Out-Null
}
If($Capture){
Copy-Item -Path "$FFUDevelopmentPath\WinPECaptureFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force
#Remove Bootfix.bin if capturing from BIOS systems
Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force
}
If($Deploy){
Copy-Item -Path "$FFUDevelopmentPath\WinPEDeployFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force
# If you need to add drivers (storage/keyboard most likely), remove the '#' from the below line and change the /Driver:Path to a folder of drivers
# & dism /image:$WinPEFFUPath\mount /Add-Driver /Driver:<Path to Drivers folder e.g c:\drivers> /Recurse
}
Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -Save
#Make ISO
$OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg"
$OSCDIMG = "$OSCDIMGPath\oscdimg.exe"
& "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\WinPE_FFU_Capture.iso
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force
}
function New-FFU {
#Need to use the Demployment and Imaging tools environment to use dism from the Insider ADK to optimize the FFU. This is only needed until Windows 23H2.
$DandIEnv = "$adkPath`Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat"
#Mount the Capture ISO to the VM
$CaptureISOPath = "$FFUDevelopmentPath\WinPE_FFU_Capture.iso"
$FFUVMs = get-vm _ffu-* | Where-Object { $_.state -ne 'running' }
If ($null -ne $FFUVMs) {
Foreach ($FFUVM in $FFUVMs) {
$VMName = $FFUVM.name
$VMDVDDrive = Get-VMDvdDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive
Set-VMDvdDrive -VMName $VMName -Path $CaptureISOPath
$VMSwitch = Get-VMSwitch -name $VMSwitchName
get-vm $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $VMSwitch.Name
#vmconnect $FFUVM.ComputerName $VMName
}
}
#Start VM
Start-VM -Name $VMName
# Wait for the VM to turn off
do {
$FFUVM = Get-VM -Name $VMName
Start-Sleep -Seconds 5
} while ($FFUVM.State -ne 'Off')
# Check for .ffu files in the FFUDevelopment folder
$FFUFiles = Get-ChildItem -Path $FFUDevelopmentPath -Filter "*.ffu" -File
# If there's more than one .ffu file, get the most recent and store its path in $FFUFile
if ($FFUFiles.Count -gt 0) {
$FFUFile = ($FFUFiles | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1).FullName
Write-Host "Most recent .ffu file: $FFUFile"
}
else {
Write-Host "No .ffu files found in $FFUFolderPath"
}
#Add drivers
If ($InstallDrivers){
New-Item -Path "$FFUDevelopmentPath\Mount" -ItemType Directory -Force
Mount-WindowsImage -ImagePath $FFUFile -Index 1 -Path "$FFUDevelopmentPath\Mount"
Add-WindowsDriver -Path "$FFUDevelopmentPath\Mount" -Driver "$FFUDevelopmentPath\Drivers" -Recurse
Dismount-WindowsImage -Path "$FFUDevelopmentPath\Mount" -Save
Remove-Item -Path "$FFUDevelopmentPath\Mount" -Recurse -Force
}
#Optimize FFU
& cmd /c """$DandIEnv"" && dism /optimize-ffu /imagefile:$FFUFile"
}
try {
#Check if the Windows ADK is installed
$adkPath = Get-ADK
}
catch {
throw $_
}
#Build ISO for Office and Apps
try{
if($InstallOffice){
Get-Office
}
if($InstallApps){
New-AppsISO
}
}
catch {
Write-Host "Getting Office and/or building the AppsISO failed"
throw $_
}
#Create VHDX
try {
$wimPath = Get-WimFromISO
$WimIndex = Get-WimIndex
$vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -Dynamic:$true
$systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk
New-MSRPartition -VhdxDisk $vhdxDisk
$osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $WimIndex[1]
$osPartitionDriveLetter = $osPartition[1].DriveLetter
$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition
Write-Host "All necessary partitions created."
Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1]
New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory
Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force
}
finally {
Dismount-ScratchVhdx -VhdxPath $VHDXPath
Dismount-DiskImage -ImagePath $ISOPath
}
#Clean up old VMs
try {
Remove-FFUVM
}
catch {
Write-Host "VM cleanup failed"
throw $_
}
#Create VM and attach VHDX
try {
$FFUVM = New-FFUVM
}
catch {
Write-Host 'VM creation failed'
throw $_
}
#Create Capture Media
try{
#This should happen while the FFUVM is building
New-PEMedia -Capture
}
catch{
throw $_
}
#Capture FFU file
try {
#Check if VM is done provisioning
do {
$FFUVM = Get-VM -Name $FFUVM.Name
Start-Sleep -Seconds 10
} while ($FFUVM.State -ne 'Off')
#Capture FFU file
New-FFU
}
Catch {
throw $_
}
@@ -1,21 +0,0 @@
rd c:\FFUDevelopment\WinPE /S /Q
cmd /c copype amd64 c:\FFUDevelopment\WinPE
Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab"
xcopy "C:\FFUDevelopment\WinPECaptureFFUFiles" c:\FFUDevelopment\WinPE\mount /Y /E
REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers
REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver:<Path to Drivers folder e.g c:\drivers> /Recurse
Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit
MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Capture.iso"
@@ -1,20 +0,0 @@
rd c:\FFUDevelopment\WinPE /S /Q
cmd /c copype amd64 c:\FFUDevelopment\WinPE
Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab"
Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab"
xcopy "C:\FFUDevelopment\WinPEDeployFFUFiles" c:\FFUDevelopment\WinPE\mount /Y /E
REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers
REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver:<Path to Drivers folder e.g c:\drivers> /Recurse
Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit
MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy.iso"
-19
View File
@@ -1,19 +0,0 @@
#Modify variables
$ISOPath = 'C:\ffu\WinPE_FFU_Capture.iso'
$vms = get-vm _ffu* | ? {$_.state -ne 'running'}
If($null -ne $vms){
Foreach ($vm in $vms){
$VMName = $vm.name
$VMDVDDrive = Get-VMDvdDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive
Set-VMDvdDrive -VMName $VMName -Path $ISOPath
$VMSwitch = Get-VMSwitch -name *intel*
get-vm $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $VMSwitch.Name
}
}
@@ -1,51 +0,0 @@
function Get-ODTURL {
[String]$MSWebPage = Invoke-RestMethod 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117'
$MSWebPage | ForEach-Object {
if ($_ -match 'url=(https://.*officedeploymenttool.*\.exe)') {
$matches[1]
}
}
}
$FFUDevelopmentPath = 'C:\FFUDevelopment'
$ODTUrl = Get-ODTURL
$ODTInstallFile = "$env:TEMP\odtsetup.exe"
Invoke-WebRequest -Uri $ODTUrl -OutFile $ODTInstallFile
# Extract Office Deployment Tool
$ODTPath = "$FFUDevelopmentPath\Apps\Office"
Start-Process -FilePath $ODTInstallFile -ArgumentList "/extract:$ODTPath /quiet" -Wait
# Run setup.exe with config.xml
$ConfigXml = "$FFUDevelopmentPath\Apps\Office\DownloadFFU.xml"
#Set-Location $ODTPath
Start-Process -FilePath "$FFUDevelopmentPath\Apps\Office\setup.exe" -ArgumentList "/download $ConfigXml" -Wait
#Make Office ISO
Remove-Item -Path "$ODTPath\configuration*" -Force
$OSCDIMG = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe'
$AppsISO = "$FFUDevelopmentPath\Apps.iso"
Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m -d $ODTPath $AppsISO" -wait
#Mount Office ISO to FFU VM
$VMS = get-vm _ffu-* | Where-Object {$_.state -eq 'running'}
foreach ($VM in $VMs) {
# Check if DVD drive exists
$DVD = Get-VMDvdDrive -VMName $VM.Name
if ($DVD) {
# Attach ISO to existing DVD drive
Set-VMDvdDrive -VMName $VM.Name -Path $AppsISO
}
else {
# Add DVD drive and attach ISO
Add-VMDvdDrive -VMName $VM.Name -Path $AppsISO
}
}
#Remove the Office Download and ODT
$OfficeDownloadPath = "$FFUDevelopmentPath\Apps\Office\Office"
Remove-Item -Path $OfficeDownloadPath -Recurse -Force
Remove-Item -Path "$ODTPath\setup.exe"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
#Modify variables
$ISOPath = 'C:\FFUDevelopment\WinPE_FFU_Capture.iso'
$VMSwitchName = '*intel*'
$vms = get-vm _ffu-* | ? {$_.state -ne 'running'}
If($null -ne $vms){
Foreach ($vm in $vms){
$VMName = $vm.name
$VMDVDDrive = Get-VMDvdDrive -VMName $VMName
Set-VMFirmware -VMName $VMName -FirstBootDevice $VMDVDDrive
Set-VMDvdDrive -VMName $VMName -Path $ISOPath
$VMSwitch = Get-VMSwitch -name $VMSwitchName
get-vm $VMName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $VMSwitch.Name
vmconnect $vm.ComputerName $VMName
}
}
@@ -1,4 +0,0 @@
select disk 0
select partition 3
Assign letter="M"
exit
@@ -1,60 +0,0 @@
#Modify the net use path to map the W: drive to the location you want to copy the FFU file to
net use W: \\192.168.1.2\c$\FFUDevelopment /user:administrator p@ssw0rd
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
#Load Registry Hive
$Software = 'M:\Windows\System32\config\software'
reg load "HKLM\FFU" $Software
#Find Windows version values
$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID'
[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild'
$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
$BuildDate = Get-Date -uformat %b%Y
$SKU = switch ($SKU){
Home {'Home'}
Professional {'Pro'}
ProfessionalEducation {'Pro_Edu'}
Enterprise {'Ent'}
Education {'Edu'}
}
if($CurrentBuild -ge 22000){
$Name = 'Win11'
}
else{
$Name = 'Win10'
}
#If Office is installed, modify the file name of the FFU
#$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null
$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office'
if($Office){
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu"
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
}
else{
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_$BuildDate.ffu"
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
}
#Unload Registry
Set-Location X:\
Remove-Variable SKU
Remove-Variable CurrentBuild
Remove-Variable DisplayVersion
Remove-Variable Office
reg unload "HKLM\FFU"
Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null
#Copy DISM log to Host
xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null
#Remvove W: drive
net use W: /delete
wpeutil Shutdown
@@ -1,5 +0,0 @@
wpeinit
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
powershell -Noprofile -ExecutionPolicy Bypass -File x:\CaptureFFU.ps1
exit
@@ -1,582 +0,0 @@
function Get-USBDrive(){
$USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter
if ($null -eq $USBDriveLetter){
#Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition
$USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter
#If we didn't get the drive letter, stop the script.
if ($null -eq $USBDriveLetter){
WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting'
Exit
}
}
$USBDriveLetter = $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 WriteLog($LogText){
Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText"
}
function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){
(Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile
}
function Set-Computername($computername){
[xml]$xml = Get-Content $UnattendFile
if($xml.unattend.settings.component.Count -ge 2){
#Assumes that Computername is the first component element
$xml.unattend.settings.component[0].ComputerName = $computername
}else{
$xml.unattend.settings.component.ComputerName = $computername
}
$xml.Save($UnattendFile)
return $computername
}
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 = $false;
}
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 - check scriptlog.txt on the USB drive for more info'
throw $_
} finally {
Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
}
}
# 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
$LogFileName = 'ScriptLog.txt'
$USBDrive = Get-USBDrive
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
$LogFile = $USBDrive + $LogFilename
WriteLog 'Begin Logging'
#Find PhysicalDrive
$PhysicalDeviceID = Get-HardDrive
WriteLog "Physical DeviceID is $PhysicalDeviceID"
#Parse DiskID Number
$DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1)
WriteLog "DiskID is $DiskID"
#Modify diskpart answer files if DiskID not 0
# $UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt'
# $ExtendPartition = 'x:\ExtendPartition-UEFI.txt'
# If ($DiskID -ne '0'){
# WriteLog 'DiskID is not 0. Need to modify diskpart answer files'
# try {
# Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID
# }
# catch {
# WriteLog "Modifying $UEFIFFUPartitions failed with error: $_"
# }
# try {
# Set-DiskpartAnswerFiles $ExtendPartition $DiskID
# }
# catch {
# WriteLog "Modifying $ExtendPartition failed with error: $_"
# }
# }
#Find FFU Files
[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu)
$FFUCount = $FFUFiles.Count
#If multiple FFUs found, ask which to install
If ($FFUCount -gt 1) {
WriteLog "Found $FFUCount FFU Files"
$array = @()
for($i=0;$i -le $FFUCount -1;$i++){
$Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName}
$array += New-Object PSObject -Property $Properties
}
$array | Format-Table -AutoSize -Property Number, FFUFile
do {
try {
$var = $true
[int]$FFUSelected = Read-Host 'Enter the FFU number to install'
$FFUSelected = $FFUSelected -1
}
catch {
Write-Host 'Input was not in correct format. Please enter a valid FFU number'
$var = $false
}
} until (($FFUSelected -le $FFUCount -1) -and $var)
$FFUFileToInstall = $array[$FFUSelected].FFUFile
WriteLog "$FFUFileToInstall was selected"
}
elseif ($FFUCount -eq 1) {
WriteLog "Found $FFUCount FFU File"
$FFUFileToInstall = $FFUFiles[0].FullName
WriteLog "$FFUFileToInstall will be installed"
}
else {
Writelog 'No FFU files found'
Write-Host 'No FFU files found'
Exit
}
#FindAP
$APFolder = $USBDrive + "Autopilot\"
If (Test-Path -Path $APFolder){
[array]$APFiles = @(Get-ChildItem -Path $APFolder*.json)
$APFilesCount = $APFiles.Count
if ($APFilesCount -ge 1){
$autopilot = $true
}
}
#FindPPKG
$PPKGFolder = $USBDrive + "PPKG\"
if (Test-Path -Path $PPKGFolder){
[array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg)
$PPKGFilesCount = $PPKGFiles.Count
if ($PPKGFilesCount -ge 1){
$PPKG = $true
}
}
#FindUnattend
$UnattendFolder = $USBDrive + "unattend\"
$UnattendFilePath = $UnattendFolder + "unattend.xml"
$UnattendPrefixPath = $UnattendFolder + "prefixes.txt"
If (Test-Path -Path $UnattendFilePath){
$UnattendFile = Get-ChildItem -Path $UnattendFilePath
If ($UnattendFile){
$Unattend = $true
}
}
If (Test-Path -Path $UnattendPrefixPath){
$UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath
If ($UnattendPrefixFile){
$UnattendPrefix = $true
}
}
#Ask for device name if unattend exists
if ($Unattend -and $UnattendPrefix){
Writelog 'Unattend file found with prefixes.txt. Getting prefixes.'
$UnattendPrefixes = @(Get-content $UnattendPrefixFile)
$UnattendPrefixCount = $UnattendPrefixes.Count
If ($UnattendPrefixCount -gt 1) {
WriteLog "Found $UnattendPrefixCount Prefixes"
$array = @()
for($i=0;$i -le $UnattendPrefixCount -1;$i++){
$Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]}
$array += New-Object PSObject -Property $Properties
}
$array | Format-Table -AutoSize -Property Number, DeviceNamePrefix
do {
try {
$var = $true
[int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name'
$PrefixSelected = $PrefixSelected -1
}
catch {
Write-Host 'Input was not in correct format. Please enter a valid prefix number'
$var = $false
}
} until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var)
$PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix
WriteLog "$PrefixToUse was selected"
}
elseif ($UnattendPrefixCount -eq 1) {
WriteLog "Found $UnattendPrefixCount Prefix"
$PrefixToUse = $UnattendPrefixes[0]
WriteLog "Will use $PrefixToUse as device name prefix"
}
#Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace
$serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim()
#Combine prefix with serial
$computername = $PrefixToUse + $serial
#If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it
If ($computername.Length -gt 15){
$computername = $computername.substring(0,15)
}
$computername = Set-Computername($computername)
Writelog "Computer name set to $computername"
}
elseif($Unattend){
Writelog 'Unattend file found with no prefixes.txt, asking for name'
[string]$computername = Read-Host 'Enter device name'
Set-Computername($computername)
Writelog "Computer name set to $computername"
}
else {
WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.'
}
#If both AP and PPKG folder found with files, ask which to use.
If($autopilot -eq $true -and $PPKG -eq $true){
WriteLog 'Both PPKG and Autopilot json files found'
Write-Host 'Both Autopilot JSON files and Provisioning packages were found.'
do {
try {
$var = $true
[int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package'
}
catch {
Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package'
$var = $false
}
} until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var)
If ($APorPPKG -eq 1){
$PPKG = $false
}
else{
$autopilot = $false
}
}
#If multiple AP json files found, ask which to install
If ($APFilesCount -gt 1 -and $autopilot -eq $true) {
WriteLog "Found $APFilesCount Autopilot json Files"
$array = @()
for($i=0;$i -le $APFilesCount -1;$i++){
$Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name}
$array += New-Object PSObject -Property $Properties
}
$array | Format-Table -AutoSize -Property Number, APFileName
do {
try {
$var = $true
[int]$APFileSelected = Read-Host 'Enter the AP json file number to install'
$APFileSelected = $APFileSelected - 1
}
catch {
Write-Host 'Input was not in correct format. Please enter a valid AP json file number'
$var = $false
}
} until (($APFileSelected -le $APFilesCount -1) -and $var)
$APFileToInstall = $array[$APFileSelected].APFile
$APFileName = $array[$APFileSelected].APFileName
WriteLog "$APFileToInstall was selected"
}
elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) {
WriteLog "Found $APFilesCount AP File"
$APFileToInstall = $APFiles[0].FullName
$APFileName = $APFiles[0].Name
WriteLog "$APFileToInstall will be copied"
}
else {
Writelog 'No AP files found or AP was not selected'
}
#If multiple PPKG files found, ask which to install
If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) {
WriteLog "Found $PPKGFilesCount PPKG Files"
$array = @()
for($i=0;$i -le $PPKGFilesCount -1;$i++){
$Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name}
$array += New-Object PSObject -Property $Properties
}
$array | Format-Table -AutoSize -Property Number, PPKGFileName
do {
try {
$var = $true
[int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install'
$PPKGFileSelected = $PPKGFileSelected - 1
}
catch {
Write-Host 'Input was not in correct format. Please enter a valid PPKG file number'
$var = $false
}
} until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var)
$PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile
WriteLog "$PPKGFileToInstall was selected"
}
elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) {
WriteLog "Found $PPKGFilesCount PPKG File"
$PPKGFileToInstall = $PPKGFiles[0].FullName
WriteLog "$PPKGFileToInstall will be used"
}
else {
Writelog 'No PPKG files found or PPKG not selected.'
}
#Find Drivers
$Drivers = $USBDrive + "Drivers"
If (Test-Path -Path $Drivers)
{
#Check if multiple driver folders found, if so, just select one folder to save time/space
$DriverFolders = Get-ChildItem -Path $Drivers
$DriverFoldersCount = $DriverFolders.count
If ($DriverFoldersCount -gt 1)
{
WriteLog "Found $DriverFoldersCount driver folders"
$array = @()
for($i=0; $i -le $DriverFoldersCount -1; $i++){
$Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName}
$array += New-Object PSObject -Property $Properties
}
$array | Format-Table -AutoSize -Property Number, Drivers
do {
try {
$var = $true
[int]$DriversSelected = Read-Host 'Enter the set of drivers to install'
$DriversSelected = $DriversSelected - 1
}
catch {
Write-Host 'Input was not in correct format. Please enter a valid driver folder number'
$var = $false
}
} until (($DriversSelected -le $DriverFoldersCount -1) -and $var)
$Drivers = $array[$DriversSelected].Drivers
WriteLog "$Drivers was selected"
}
elseif ($DriverFoldersCount -eq 1) {
WriteLog "Found $DriverFoldersCount driver folder"
$Drivers = $DriverFolders.FullName
WriteLog "$Drivers will be installed"
}
else {
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
Writelog 'Clean Disk'
#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append
#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions"
try {
$Disk = Get-Disk -Number $DiskID
$Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false
}
catch {
WriteLog 'Cleaning disk failed. Exiting'
throw $_
}
Writelog 'Cleaning Disk succeeded'
#Apply FFU
WriteLog "Applying FFU to $PhysicalDeviceID"
WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID"
#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen.
dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID
if($LASTEXITCODE -eq 0){
WriteLog 'Successfully applied FFU'
}
else{
Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info"
#Copy DISM log to USBDrive
invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y"
exit
}
#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed
# $disk = get-disk -Number $DiskID
# $RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'}
# if ($RecoveryPartition){
# $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber
# if ($RecoveryPartitionNumber -eq 4){
# try {
# WriteLog 'Removing recovery partition'
# Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false
# }
# catch {
# WriteLog 'Error removing recovery partition, exiting'
# throw $_
# }
# }
# else{
# WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.'
# exit
# }
# }
#Extend Windows partition and create recovery partition
# Writelog 'Extending Windows partition'
# Invoke-Process diskpart.exe "/S $ExtendPartition"
# if($LASTEXITCODE -eq 0){
# WriteLog 'Successfully extended Windows partition and created recovery partition'
# }
# else{
# Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE"
# }
#Set W: drive letter to Windows partition
Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object PartitionNumber -eq 3 | Set-Partition -NewDriveLetter W
#Copy modified WinRE if folder exists, else copy inbox WinRE
$WinRE = $USBDrive + "WinRE\winre.wim"
If (Test-Path -Path $WinRE)
{
WriteLog 'Copying modified WinRE to Recovery directory'
Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y"
WriteLog 'Copying WinRE to Recovery directory succeeded'
WriteLog 'Registering location of recovery tools'
Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows"
WriteLog 'Registering location of recovery tools succeeded'
}
# else
# {
# WriteLog 'Copying default WinRE to Recovery directory'
# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y"
# WriteLog 'Copying WinRE to Recovery directory succeeded'
# WriteLog 'Registering location of recovery tools'
# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows"
# WriteLog 'Registering location of recovery tools succeeded'
# }
#Autopilot JSON
If ($APFileToInstall){
WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot"
Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\"
WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded"
# Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json
try {
Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json'
WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json"
}
catch{
Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_"
throw $_
}
}
#Apply PPKG
If ($PPKGFileToInstall){
try {
#Make sure to delete any existing PPKG on the USB drive
Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object {
Remove-item -Path $_.FullName
}
WriteLog "Copying $PPKGFileToInstall to $USBDrive"
Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive"
WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded"
}
catch{
Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_"
throw $_
}
}
#Set DeviceName
If ($PrefixToUse){
try{
$PantherDir = 'w:\windows\panther'
If (Test-Path -Path $PantherDir){
Writelog "Copying $UnattendFile to $PantherDir"
Invoke-process xcopy "$UnattendFile $PantherDir /Y"
WriteLog "Copying $UnattendFile to $PantherDir succeeded"
}
else{
Writelog "$PantherDir doesn't exist, creating it"
New-Item -Path $PantherDir -ItemType Directory -Force
Writelog "Copying $UnattendFile to $PantherDir"
Invoke-Process xcopy.exe "$UnattendFile $PantherDir"
WriteLog "Copying $UnattendFile to $PantherDir succeeded"
}
}
catch{
WriteLog "Copying Unattend.xml to name device failed"
throw $_
}
}
#Add Drivers
#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances.
If (Test-Path -Path $Drivers)
{
WriteLog 'Copying drivers'
Write-Warning 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. This is done so drivers are logged to the scriptlog.txt file. Please be patient.'
Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse"
WriteLog 'Copying drivers succeeded'
}
#Copy DISM log to USBDrive
WriteLog "Copying dism log to $USBDrive"
invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y"
WriteLog "Copying dism log to $USBDrive succeeded"
@@ -1,5 +0,0 @@
wpeinit
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
powershell -Noprofile -ExecutionPolicy Bypass -File x:\ApplyFFU.ps1
exit
@@ -1,556 +0,0 @@
#Requires -Modules Hyper-V, Storage
#Requires -PSEdition Desktop
<#
.NOTES
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
.SYNOPSIS
Creates an FFU with proper disk layout given an input WIM
.DESCRIPTION
Creates an FFU with proper disk layout given an input WIM
.PARAMETER WimPath
The path to the WIM to be converted into an FFU
.PARAMETER WimIndex
The index of the image within the WIM to be used
.PARAMETER FfuPath
Output path of the FFU to be created
.PARAMETER ScratchVhdxPath
Output path of the scratch VHDX which will be created as an intermediate-step to the process of creating the FFU.
.PARAMETER SaveScratchVhdx
Keep the scratch VHDX after capturing the FFU. This VHDX can be used to create a VM for testing.
.PARAMETER SizeBytes
Size in bytes of the disk the FFU is to be applied to.
With an Optimized FFU, the FFU can then be applied to any disk large enough for the data within the FFU.
.PARAMETER LogicalSectorSizeBytes
Logical sector size to store data within the FFU.
This should match the logical sector size of the disks this FFU is applied to.
.PARAMETER Dynamic
Whether the scratch VHDX is to be a dynamically sized file or a fixed file size to match the maximum size of the VHDX.
.PARAMETER PartitionStyle
GPT or MBR partition style for the final disk layout.
.PARAMETER SkipSystemPartition
Skips creating a System partition. Boot files will be placed on the OS partition.
.PARAMETER SystemPartitionSize
If creating a System partition, specifies the size in bytes of that partition.
.PARAMETER SkipMSRPartition
Skips creating an MSR partition.
.PARAMETER OSPartitionSize
Allows the specification of the OS partition size. If left at default, the OS partition will take all available space,
minus what is needed by the Recovery partition, if any.
If a Data partition is specified, an OS partition size must be specified.
.PARAMETER AddDataPartition
Allows the addition of an extra Data partition, separate from the OS partition.
.PARAMETER DataPartitionSize
Allows specifying the size of the Data partition, if the -AddDataPartition flag is used.
.PARAMETER SkipRecoveryPartition
Skips the creation of a Recovery partition, used to store the Windows Recovery Environment (WinRE.wim).
.PARAMETER RecoveryPartitionSize
Specifies the size of the Recovery partition to be created.
If left at default, the partition will be the size of the WinRE.wim in the OS partition plus 52 MB plus a buffer of 32 MB
since free space of WinRE.wim + 52 MB is needed.
.PARAMETER FFUDriveName
Name passed to DISM's /Capture-FFU /Name parameter, used to set a name for the FFU, separate from the file name.
.PARAMETER FFUCompression
Specifies FFU compression of Default or None.
.PARAMETER FirmwareType
Specifies to create boot files for the disk for a firmware of BIOS, UEFI, or both (ALL).
.PARAMETER OptimizeFfu
Creates an Optimized FFU which can be applied to a disk of a different size than the original FFU disk size
as long as the disk it is applied to is large enough to fit the data within the FFU.
Optimized FFUs are only available on Windows version 1903 and higher.
.PARAMETER Force
Forces the overwriting of existing scratch VHDX or FFUs if the script is run multiple times
or specifying a path with an existing VHDX or FFU of the same name.
.EXAMPLE
.\Convert-WimToFfu.ps1 -WimPath .\install.wim
Creates an FFU named install.ffu in the same directory as the passed install.wim
.EXAMPLE
.\Convert-WimToFfu.ps1 -WimPath .\install.wim -WimIndex 1 -FfuPath .\flash.ffu
Creates an FFU from the Windows image at index 1 within install.wim and names the FFU "flash.ffu"
.EXAMPLE
.\Convert-WimToFfu.ps1 -WimPath .\install.wim -WimIndex 1 -FfuPath .\flash.ffu -SizeBytes 64GB
Creates an FFU that can only be applied on "64GB" disks.
Keep in mind that 64GB is 68,719,476,736 bytes which may be larger than the target disks.
.EXAMPLE
.\Convert-WimToFfu.ps1 -WimPath .\install.wim -WimIndex 1 -FfuPath .\flash.ffu -OptimizeFfu
Creates an FFU which can be applied to disks of a different size than the original FFU disk size.
#>
param(
[Parameter(Mandatory = $true, Position = 0)]
[Alias("Path")]
[ValidateScript({ Test-Path $_ })]
[string]$WimPath,
[uint32]$WimIndex = 1,
[string]$FfuPath,
[string]$ScratchVhdxPath,
[switch]$SaveScratchVhdx,
[uint64]$SizeBytes = 31000000000,
[ValidateSet(512, 4096)]
[uint32]$LogicalSectorSizeBytes = 512,
[switch]$Dynamic,
[Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]$PartitionStyle = [Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]::GPT,
[switch]$SkipSystemPartition,
[uint64]$SystemPartitionSize = 256MB,
[switch]$SkipMSRPartition,
[uint64]$OSPartitionSize = 0,
[switch]$AddDataPartition,
[uint64]$DataPartitionSize = 0,
[switch]$SkipRecoveryPartition,
[uint64]$RecoveryPartitionSize = 0,
[string]$FFUDriveName = "WimToFfu",
[ValidateSet("Default", "None")]
[string]$FFUCompression = "Default",
[ValidateSet("UEFI", "BIOS", "ALL")]
[string]$FirmwareType = "UEFI",
[switch]$OptimizeFFU,
[switch]$Force
);
#region FUNCTIONS
function Add-BootFiles
{
param(
[Parameter(Mandatory = $true)]
[string]$OsPartitionDriveLetter,
[Parameter(Mandatory = $true)]
[string]$SystemPartitionDriveLetter
);
Write-Host "Adding boot files for `"$($OsPartitionDriveLetter):\Windows`" to System partition `"$($SystemPartitionDriveLetter):`"...";
bcdboot "$($OsPartitionDriveLetter):\Windows" /S "$($SystemPartitionDriveLetter):" /F "$FirmwareType";
Write-Host "Done.";
}
function Get-RecoveryPartition
{
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[Parameter(Mandatory = $true)]
[ciminstance]$OsPartition,
[uint64]$RecoveryPartitionSize = 0,
[ciminstance]$DataPartition
);
Write-Host "Creating empty Recovery partition (to be filled on first boot automatically)...";
$calculatedRecoverySize = 0;
$recoveryPartition = $null;
if($RecoveryPartitionSize -gt 0)
{
$calculatedRecoverySize = $RecoveryPartitionSize;
}
else
{
$winReWim = Get-ChildItem "$($OsPartition.DriveLetter):\Windows\System32\Recovery\Winre.wim";
if(($winReWim -ne $null) -and ($winReWim.Count -eq 1))
{
# Wim size + 52MB is minimum WinRE partition size.
# NTFS and other partitioning size differences account for about 17MB of space that's unavailable.
# Adding 32MB as a buffer to ensure there's enough space.
$calculatedRecoverySize = $winReWim.Length + 52MB + 32MB;
Write-Host "Calculated space needed for recovery in bytes: $calculatedRecoverySize";
if($DataPartition -ne $null)
{
$DataPartition | Resize-Partition -Size ($DataPartition.Size - $calculatedRecoverySize);
Write-Host "Data partition shrunk by $calculatedRecoverySize bytes for Recovery partition.";
}
else
{
$OsPartition | Resize-Partition -Size ($OsPartition.Size - $calculatedRecoverySize);
Write-Host "OS partition shrunk by $calculatedRecoverySize bytes for Recovery partition.";
}
$recoveryPartition = $VhdxDisk | New-Partition -AssignDriveLetter -UseMaximumSize -GptType "{de94bba4-06d1-4d40-a16a-bfd50179d6ac}" `
| Format-Volume -FileSystem NTFS -Confirm:$false -Force -NewFileSystemLabel "WinRE";
Write-Host "Done. Recovery partition at drive $($recoveryPartition.DriveLetter):";
}
else
{
Write-Host "No WinRE.WIM found in the OS partition under \Windows\System32\Recovery.";
Write-Host "Skipping creating the Recovery partition.";
Write-Host "If a Recovery partition is desired, please re-run the script setting the -RecoveryPartitionSize flag as appropriate."
}
}
return $recoveryPartition;
}
function Get-DataPartition
{
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[uint64]$DataPartitionSize = 0
);
Write-Host "Creating Data partition...";
$dataPartition = $null;
if(($OSPartitionSize -ne $null) -and ($OSPartitionSize -gt 0))
{
if(($DataPartitionSize -ne $null) -and ($DataPartitionSize -gt 0))
{
$dataPartition = $vhdxDisk | New-Partition -AssignDriveLetter -Size $DataPartitionSize;
}
else
{
$dataPartition = $vhdxDisk | New-Partition -AssignDriveLetter -UseMaximumSize;
}
}
else
{
Write-Host "To add a data partition, OS partition size must be set. Skipping adding data partition...";
}
Write-Host "Done. Data partition at drive $($dataPartition.DriveLetter):";
return $dataPartition;
}
function Get-OSPartition
{
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[Parameter(Mandatory = $true)]
[string]$WimPath,
[uint32]$WimIndex = 1,
[uint64]$OSPartitionSize = 0
);
Write-Host "Creating OS partition...";
if($OSPartitionSize -gt 0)
{
$osPartition = $vhdxDisk | New-Partition -AssignDriveLetter -Size $OSPartitionSize;
}
else
{
$osPartition = $vhdxDisk | New-Partition -AssignDriveLetter -UseMaximumSize;
}
$formattedOsPartition = $osPartition | Format-Volume -FileSystem NTFS -Confirm:$false -Force -NewFileSystemLabel "Windows";
Write-Host "Done. OS partition at drive $($osPartition.DriveLetter):";
Write-Host "Writing WIM at $WimPath to OS partition at drive $($osPartition.DriveLetter):...";
#Server 2019 is missing the Windows Overlay Filter (wof.sys), likely other Server SKUs are missing it as well. Script will error if trying to use the -compact switch on Server OSes
if((Get-CimInstance Win32_OperatingSystem).Caption -match "Server"){
Write-Host (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\");
}
else {
Write-Host (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\" -Compact);
}
Write-Host "Done.";
return $osPartition;
}
function Get-MSRPartition
{
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk
);
Write-Host "Creating MSR partition...";
$toReturn = $VhdxDisk | New-Partition -AssignDriveLetter -Size 16MB -GptType "{e3c9e316-0b5c-4db8-817d-f92df00215ae}" -IsHidden;
Write-Host "Done.";
return $toReturn;
}
function Get-SystemPartition
{
param(
[Parameter(Mandatory = $true)]
[ciminstance]$VhdxDisk,
[uint64]$SystemPartitionSize = 100MB
);
Write-Host "Creating System partition...";
$sysPartition = $VhdxDisk | New-Partition -AssignDriveLetter -Size $SystemPartitionSize -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" -IsHidden;
$formattedSysPartition = $sysPartition | Format-Volume -FileSystem FAT32 -Force -NewFileSystemLabel "System";
Write-Host "Done. System partition at drive $($sysPartition.DriveLetter):";
return $sysPartition.DriveLetter;
}
function Dismount-ScratchVhdx
{
param(
[Parameter(Mandatory = $true)]
[string]$VhdxPath
);
if(Test-Path $VhdxPath)
{
Write-Host "Dismounting scratch VHDX...";
Dismount-VHD -Path $VhdxPath;
Write-Host "Done.";
}
}
function Get-ScratchVhdx
{
param(
[Parameter(Mandatory = $true)]
[string]$VhdxPath,
[uint64]$SizeBytes = 64000000000,
[ValidateSet(512, 4096)]
[uint32]$LogicalSectorSizeBytes = 512,
[switch]$Dynamic,
[Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]$PartitionStyle = [Microsoft.PowerShell.Cmdletization.GeneratedTypes.Disk.PartitionStyle]::GPT,
[switch]$AddRecoveryPartition,
[uint64]$RecoveryPartitionSize = 0
);
Write-Host "Creating new Scratch VHDX...";
$newVHDX = New-VHD -Path $VhdxPath -SizeBytes $SizeBytes -LogicalSectorSizeBytes $LogicalSectorSizeBytes -Dynamic:($Dynamic.IsPresent);
$toReturn = $newVHDX | Mount-VHD -Passthru | Initialize-Disk -PassThru -PartitionStyle $PartitionStyle;
Write-Host "Done.";
return $toReturn;
}
function Get-OutputFilePath
{
param(
[Parameter(Mandatory = $true)]
[string]$WimPath,
[string]$OutputFilePath,
[Parameter(Mandatory = $true)]
[string]$ParamName,
[Parameter(Mandatory = $true)]
[string]$Extension,
[Parameter(Mandatory = $true)]
[bool]$Force
);
if([string]::IsNullOrEmpty($OutputFilePath))
{
$OutputFilePath = [System.IO.Path]::ChangeExtension($WimPath, $Extension);
}
if((Test-Path $OutputFilePath) -and (-not $Force))
{
throw New-Object System.ArgumentException("Unable to overwrite existing file $OutputFilePath without -Force flag.", $ParamName);
}
return $OutputFilePath;
}
function Write-If
{
param(
[Parameter(Mandatory = $true)]
[bool]$Condition,
[Parameter(Mandatory = $true)]
[string]$MessageIfTrue,
[Parameter(Mandatory = $true)]
[string]$MessageIfFalse
);
if($Condition)
{
Write-Host $MessageIfTrue;
}
else
{
Write-Host $MessageIfFalse;
}
}
#endregion
#region MAIN SCRIPT BODY
#region PRINT INPUT PARAMETERS
Write-Host "Using WIM path: $WimPath.";
$FfuPath = Get-OutputFilePath -WimPath $WimPath -OutputFilePath $FfuPath -ParamName "FfuPath" -Extension "ffu" -Force $Force.IsPresent;
Write-Host "Using FFU path: $FfuPath.";
$ScratchVhdxPath = Get-OutputFilePath -WimPath $WimPath -OutputFilePath $ScratchVhdxPath -ParamName "ScratchVhdxPath" -Extension "vhdx" -Force $Force.IsPresent;
Write-Host "Using VHDX path: $ScratchVhdxPath.";
Write-Host "Using WIM Index: $WimIndex.";
Write-If -Condition $SaveScratchVhdx.IsPresent `
-MessageIfTrue "Will save intermediate scratch VHDX." `
-MessageIfFalse "Will delete intermediate scratch VHDX.";
Write-Host "Using Disk Size (bytes) of $SizeBytes.";
Write-Host "Using Logical Sector Size (bytes) of $LogicalSectorSizeBytes.";
Write-If -Condition $Dynamic.IsPresent `
-MessageIfTrue "Intermediate scratch VHDX on disk will be Dynamically sized." `
-MessageIfFalse "Intermediate scratch VHDX on disk will be Fixed sized.";
Write-Host "Partition style will be $PartitionStyle.";
Write-If -Condition $SkipSystemPartition.IsPresent `
-MessageIfTrue "Will not add System partition." `
-MessageIfFalse "Will add System partition of size (bytes) $SystemPartitionSize.";
Write-If -Condition $SkipMSRPartition.IsPresent `
-MessageIfTrue "Will not add MSR partition." `
-MessageIfFalse "Will add 16 MB MSR partition.";
Write-If -Condition ($OSPartitionSize -eq 0) `
-MessageIfTrue "Will create maximum-sized OS partition." `
-MessageIfFalse "Will create OS partition of size (bytes) $OSPartitionSize.";
Write-If -Condition $AddDataPartition.IsPresent `
-MessageIfTrue "Will add extra Data partition of size (bytes) $DataPartitionSize." `
-MessageIfFalse "Will not add extra Data partition.";
Write-If -Condition $SkipRecoveryPartition.IsPresent `
-MessageIfTrue "Will not add Recovery partition." `
-MessageIfFalse "Will add Recovery partition.";
Write-If -Condition $OptimizeFFU.IsPresent `
-MessageIfTrue "Will run DISM's /Optimize-FFU command." `
-MessageIfFalse "Will skip DISM's /Optimize-FFU command.";
if(-not ($SkipSystemPartition.IsPresent))
{
if($RecoveryPartitionSize -eq 0)
{
Write-Host "Will use default, calculated Recovery partition size (WinRE.WIM size + 52 MB + plus a buffer of 32 MB due to NTFS).";
}
else
{
Write-Host "Will add Recovery partition of size (bytes) $RecoveryPartitionSize.";
}
}
Write-Host "Using FFU Drive name of $FFUDriveName.";
Write-Host "Using FFU compression of $FFUCompression.";
Write-Host "Using Firmware Type (for boot files) of $FirmwareType.";
Write-If -Condition $Force.IsPresent `
-MessageIfTrue "Force flag is present. Overwriting files when necessary." `
-MessageIfFalse "Force flag is not present. Will not overwrite existing files.";
#endregion PRINT INPUT PARAMETERS
try
{
$vhdxDisk = Get-ScratchVhdx -VhdxPath $ScratchVhdxPath -SizeBytes $SizeBytes -LogicalSectorSizeBytes $LogicalSectorSizeBytes -Dynamic:($Dynamic.IsPresent) -PartitionStyle $PartitionStyle;
if(-not ($SkipSystemPartition.IsPresent))
{
$systemPartitionDriveLetter = Get-SystemPartition -VhdxDisk $vhdxDisk -SystemPartitionSize $SystemPartitionSize;
}
if(-not ($SkipMSRPartition.IsPresent))
{
$msrPartition = Get-MSRPartition -VhdxDisk $vhdxDisk;
}
$osPartition = Get-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $WimIndex;
if($AddDataPartition.IsPresent)
{
$dataPartition = Get-DataPartition -VhdxDisk $vhdxDisk -DataPartitionSize $DataPartitionSize;
}
if(-not($SkipRecoveryPartition.IsPresent))
{
$recoveryPartition = Get-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition;
}
Write-Host "All necessary partitions created.";
if($SkipSystemPartition.IsPresent)
{
Add-BootFiles -OsPartitionDriveLetter $osPartition.DriveLetter -SystemPartitionDriveLetter $osPartition.DriveLetter;
}
else
{
Add-BootFiles -OsPartitionDriveLetter $osPartition.DriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter;
}
Write-Host "Capturing scratch VHDX into FFU...";
dism /Capture-FFU /ImageFile:"$FfuPath" /CaptureDrive:"\\.\PhysicalDrive$($vhdxDisk.DiskNumber)" /Name:"$FFUDriveName" /Compress:"$FFUCompression"
Write-Host "Done.";
}
finally
{
Dismount-ScratchVhdx -VhdxPath $ScratchVhdxPath;
}
if($SaveScratchVhdx.IsPresent)
{
Write-Host "Scratch VHDX has been kept at $ScratchVhdxPath";
}
else
{
Remove-Item -Path $ScratchVhdxPath -Force -Confirm:$false;
Write-Host "Scratch VHDX has been deleted.";
}
if($OptimizeFFU.IsPresent)
{
Write-Host "Running DISM /Optimize-FFU /ImageFile:$FfuPath...";
dism /Optimize-FFU /ImageFile:"$FfuPath"
Write-Host "Done.";
}
else
{
Write-Host "Skipping running DISM /Optimize-FFU.";
}
Write-Host "Convert-WimToFfu.ps1 script complete.";
#endregion