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 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>
|
||||
+1320
-41
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -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
|
||||
@@ -15,10 +15,12 @@ $DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\Cu
|
||||
$BuildDate = Get-Date -uformat %b%Y
|
||||
|
||||
$SKU = switch ($SKU) {
|
||||
Home {'Home'}
|
||||
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
|
||||
|
||||
@@ -6,6 +6,8 @@ This process will copy Windows in about 2-3 minutes to the target device, option
|
||||
While we use this in Education at Microsoft, other industries can use it as well. We esepcially see a need for something like this with partners who do re-imaging on behalf of customers. The difference in Education is that they typically have large deployments that tend to happen at the beginning of the school year and any amount of time saved is helpful. Microsoft Deployment Toolkit, Configuration Manager, and other community solutions are all great solutions, but are typically slower due to WIM deployments being file-based while FFU files are sector-based.
|
||||
|
||||
# Updates
|
||||
2023-05-22
|
||||
- Automated most of the process
|
||||
2023-03-03
|
||||
- Added script convert-wimToFFU.ps1 which will convert any WIM file to a FFU. If you don't want to capture apps in your FFU, this is a much quicker way to get a FFU file to deploy. Check the ConvertWimToFFU.docx file for more info.
|
||||
|
||||
|
||||
@@ -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 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 $_
|
||||
}
|
||||
@@ -9,25 +9,25 @@ function Get-ODTURL {
|
||||
}
|
||||
}
|
||||
|
||||
$FFUDevPath = 'C:\FFUDevelopment'
|
||||
$FFUDevelopmentPath = 'C:\FFUDevelopment'
|
||||
$ODTUrl = Get-ODTURL
|
||||
$ODTInstallFile = "$env:TEMP\odtsetup.exe"
|
||||
Invoke-WebRequest -Uri $ODTUrl -OutFile $ODTInstallFile
|
||||
|
||||
# Extract Office Deployment Tool
|
||||
$ODTPath = "$FFUDevPath\Office"
|
||||
$ODTPath = "$FFUDevelopmentPath\Apps\Office"
|
||||
Start-Process -FilePath $ODTInstallFile -ArgumentList "/extract:$ODTPath /quiet" -Wait
|
||||
|
||||
# Run setup.exe with config.xml
|
||||
$ConfigXml = "$FFUDevPath\Office\DownloadFFU.xml"
|
||||
$ConfigXml = "$FFUDevelopmentPath\Apps\Office\DownloadFFU.xml"
|
||||
#Set-Location $ODTPath
|
||||
Start-Process -FilePath "$FFUDevPath\Office\setup.exe" -ArgumentList "/download $ConfigXml" -Wait
|
||||
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'
|
||||
$OfficeISO = "$FFUDevPath\office.iso"
|
||||
Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m $ODTPath $OfficeISO" -wait
|
||||
$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'}
|
||||
@@ -37,16 +37,15 @@ foreach ($VM in $VMs) {
|
||||
$DVD = Get-VMDvdDrive -VMName $VM.Name
|
||||
if ($DVD) {
|
||||
# Attach ISO to existing DVD drive
|
||||
Set-VMDvdDrive -VMName $VM.Name -Path $OfficeISO
|
||||
Set-VMDvdDrive -VMName $VM.Name -Path $AppsISO
|
||||
}
|
||||
else {
|
||||
# Add DVD drive and attach ISO
|
||||
Add-VMDvdDrive -VMName $VM.Name -Path $OfficeISO
|
||||
Add-VMDvdDrive -VMName $VM.Name -Path $AppsISO
|
||||
}
|
||||
}
|
||||
|
||||
#Remove the Office Download and ODT
|
||||
$OfficeDownloadPath = "$FFUDevPath\Office\Office"
|
||||
$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.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user