revamped automation

This commit is contained in:
rbalsleyMSFT
2023-05-22 16:41:08 -07:00
parent 6883457571
commit d6e5bff58b
33 changed files with 2721 additions and 74 deletions
@@ -0,0 +1,13 @@
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 Contoso App (Example)
REM msiexec /i d:\Contoso\setup.msi /qn /norestart
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
@@ -6,6 +6,8 @@
<ExcludeApp ID="Lync" />
<ExcludeApp ID="Publisher" />
<ExcludeApp ID="Bing" />
<ExcludeApp ID="Teams" />
<ExcludeApp ID="Outlook" />
</Product>
</Add>
<Property Name="SharedComputerLicensing" Value="0" />
@@ -1,5 +1,5 @@
<Configuration ID="efa6df21-a106-428e-8eaa-d89a5dda6030">
<Add SourcePath = "C:\FFUDevelopment\Office" OfficeClientEdition="64" Channel="MonthlyEnterprise">
<Add SourcePath="C:\FFUDevelopment\Apps\Office" OfficeClientEdition="64" Channel="MonthlyEnterprise">
<Product ID="O365ProPlusRetail">
<Language ID="MatchOS" />
<ExcludeApp ID="Access" />
@@ -0,0 +1,21 @@
<?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>
File diff suppressed because it is too large Load Diff
-21
View File
@@ -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
}
}
-52
View File
@@ -1,52 +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]
}
}
}
$FFUDevPath = 'C:\FFUDevelopment'
$ODTUrl = Get-ODTURL
$ODTInstallFile = "$env:TEMP\odtsetup.exe"
Invoke-WebRequest -Uri $ODTUrl -OutFile $ODTInstallFile
# Extract Office Deployment Tool
$ODTPath = "$FFUDevPath\Office"
Start-Process -FilePath $ODTInstallFile -ArgumentList "/extract:$ODTPath /quiet" -Wait
# Run setup.exe with config.xml
$ConfigXml = "$FFUDevPath\Office\DownloadFFU.xml"
#Set-Location $ODTPath
Start-Process -FilePath "$FFUDevPath\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'
$OfficeISO = "$FFUDevPath\office.iso"
Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m $ODTPath $OfficeISO" -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 $OfficeISO
}
else {
# Add DVD drive and attach ISO
Add-VMDvdDrive -VMName $VM.Name -Path $OfficeISO
}
}
#Remove the Office Download and ODT
$OfficeDownloadPath = "$FFUDevPath\Office\Office"
Remove-Item -Path $OfficeDownloadPath -Recurse -Force
Remove-Item -Path "$ODTPath\setup.exe"
Binary file not shown.
Binary file not shown.
-21
View File
@@ -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
}
}
-17
View File
@@ -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,4 +0,0 @@
d:\setup.exe /configure d:\DeployFFU.xml
taskkill /IM sysprep.exe
timeout /t 5
c:\windows\system32\sysprep\sysprep.exe /quiet /generalize /oobe
@@ -1,5 +1,5 @@
#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
#Modify the net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user 62bffb7c-4350-426c-8151-58093bb90117
net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user 62bffb7c-4350-426c-8151-58093bb90117
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
@@ -14,11 +14,13 @@ $SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersio
$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'}
$SKU = switch ($SKU) {
Core { 'Home' }
Professional { 'Pro' }
ProfessionalEducation { 'Pro_Edu' }
Enterprise { 'Ent' }
Education { 'Edu' }
ProfessionalWorkstation { 'Pro_Wks' }
}
if($CurrentBuild -ge 22000){
@@ -30,7 +32,7 @@ else{
#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'
$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue
if($Office){
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu"
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
@@ -38,7 +40,7 @@ if($Office){
}
else{
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_$BuildDate.ffu"
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Apps`_$BuildDate.ffu"
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
}
@@ -54,4 +56,7 @@ 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: \\192.168.1.158\FFUCaptureShare /user:ffu_user 62bffb7c-4350-426c-8151-58093bb90117
wpeutil Shutdown
-4
View File
@@ -1,4 +0,0 @@
1234-
5678-
ABCD
WXYZ
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" 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">
<ComputerName>MyComputer</ComputerName>
</component>
</settings>
</unattend>
@@ -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