mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
revamped automation
This commit is contained in:
@@ -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 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
|
||||
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,60 @@
|
||||
#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
|
||||
@@ -0,0 +1,563 @@
|
||||
#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 $_
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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"
|
||||
@@ -0,0 +1,19 @@
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
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.
@@ -0,0 +1,21 @@
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
select disk 0
|
||||
select partition 3
|
||||
Assign letter="M"
|
||||
exit
|
||||
@@ -0,0 +1,60 @@
|
||||
#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
|
||||
@@ -0,0 +1,5 @@
|
||||
wpeinit
|
||||
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
|
||||
powershell -Noprofile -ExecutionPolicy Bypass -File x:\CaptureFFU.ps1
|
||||
exit
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
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"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
wpeinit
|
||||
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
|
||||
powershell -Noprofile -ExecutionPolicy Bypass -File x:\ApplyFFU.ps1
|
||||
exit
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
1234-
|
||||
5678-
|
||||
ABCD
|
||||
WXYZ
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,556 @@
|
||||
#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
|
||||
|
||||
Reference in New Issue
Block a user