mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Changes
- Updated parameter definition block to be alphabetized (not to be confused by the param block, which is not alphabetized) - Added $PEDriversFolder script variable to the param block (for some reason it was missing) - Added ConfigFile and ExportConfigFile parameters to support json config files - Changed Version to 2412.1 - Modified vhdxCacheItem class to include $LogicalSectorSizeBytes - Added new function Get-Parameters to help with new config and export config file functionality - Fixed Get-MicrosoftDrivers function to not require the HTMLFILE COM object, which isn't available in Windows 11. It seems to be installed with Office, which is what was allowing downloads to work and masked the issue. - Added long path support to prevent issues with oscdimg creating the Apps.iso. - Fixed an issue where the $PEDriversFolder variable wasn't being used (instead $FFUDevelopment\PEDrivers was used) - Created a new function New-FFUFileName - this works in conjunction with the new $CustomFFUNameTemplate. The function was needed to support both scenarios where $InstallApps is either $true or $false. - Added new function Export-ConfigFile. When passing -ExportConfigFile 'Path\To\ConfigFile.json' the script will generate a parameter dump of all of the configured parameters - Added driver folder validation to throw an error if spaces are detected in the folder name of the drivers folder (e.g. C:\FFUDevelopment\Drivers\Dell 3190). This is due to an issue with Dell drivers and their inability to handle paths with spaces consistently. - Added back the Windows Security Platform update which grabs it from the web instead of the Microsoft update catalog - Fixed an issue where the Drivers folder was being completely deleted instead of its sub-folders - Removed the Requires -PSEdition Desktop. The script works with both Desktop and Core, so pwsh 7 is fine. - Created a new config folder to hold config files. A new sample_default.json file is provided to show what the format looks like. - You can now set the computername in the unattend.xml file whereever you want. Prior it required that the computername was the first component element.
This commit is contained in:
+447
-237
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
#Requires -Modules Hyper-V, Storage
|
#Requires -Modules Hyper-V, Storage
|
||||||
#Requires -PSEdition Desktop
|
|
||||||
#Requires -RunAsAdministrator
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
<#
|
<#
|
||||||
@@ -10,89 +9,38 @@ A PowerShell script to create a Windows 10/11 FFU file.
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
This script creates a Windows 10/11 FFU and USB drive to help quickly get a Windows device reimaged. FFU can be customized with drivers, apps, and additional settings.
|
This script creates a Windows 10/11 FFU and USB drive to help quickly get a Windows device reimaged. FFU can be customized with drivers, apps, and additional settings.
|
||||||
|
|
||||||
.PARAMETER ISOPath
|
.PARAMETER AllowExternalHardDiskMedia
|
||||||
Path to the Windows 10/11 ISO file.
|
When set to $true, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined.
|
||||||
|
|
||||||
.PARAMETER WindowsSKU
|
.PARAMETER AllowVHDXCaching
|
||||||
Edition of Windows 10/11 to be installed, e.g., accepted values are: 'Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N'
|
When set to $true, will cache the VHDX file and create a config json file that will keep track of the Windows build information, the updates installed, and the logical sector byte size information. Default is $false.
|
||||||
|
|
||||||
.PARAMETER FFUDevelopmentPath
|
.PARAMETER AppsScriptVariables
|
||||||
Path to the FFU development folder (default is C:\FFUDevelopment).
|
When passed a hashtable, the script will alter the $FFUDevelopmentPath\Apps\InstallAppsandSysprep.cmd file to set variables with the hashtable keys as variable names and the hashtable values their content.
|
||||||
|
|
||||||
.PARAMETER InstallApps
|
|
||||||
When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.ISO, install the Apps, sysprep, and capture the VM. When set to $False, the FFU is created from a VHDX file. No VM is created.
|
|
||||||
|
|
||||||
.PARAMETER InstallOffice
|
|
||||||
Install Microsoft Office if set to $true. The script will download the latest ODT and Office files in the $FFUDevelopmentPath\Apps\Office folder and install Office in the FFU via VM
|
|
||||||
|
|
||||||
.PARAMETER InstallDrivers
|
|
||||||
Install device drivers from the specified $FFUDevelopmentPath\Drivers folder if set to $true. Download the drivers and put them in the Drivers folder. The script will recurse the drivers folder and add the drivers to the FFU.
|
|
||||||
|
|
||||||
.PARAMETER Memory
|
|
||||||
Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Use 4GB if necesary.
|
|
||||||
|
|
||||||
.PARAMETER Disksize
|
|
||||||
Size of the virtual hard disk for the virtual machine. Default is a 30GB dynamic disk.
|
|
||||||
|
|
||||||
.PARAMETER Processors
|
|
||||||
Number of virtual processors for the virtual machine. Recommended to use at least 4.
|
|
||||||
|
|
||||||
.PARAMETER VMSwitchName
|
|
||||||
Name of the Hyper-V virtual switch. If $InstallApps is set to $true, this must be set. This is required to capture the FFU from the VM. The default is *external*, but you will likely need to change this.
|
|
||||||
|
|
||||||
.PARAMETER VMLocation
|
|
||||||
Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to.
|
|
||||||
|
|
||||||
.PARAMETER FFUPrefix
|
|
||||||
Prefix for the generated FFU file. Default is _FFU
|
|
||||||
|
|
||||||
.PARAMETER FFUCaptureLocation
|
|
||||||
Path to the folder where the captured FFU will be stored. Default is $FFUDevelopmentPath\FFU
|
|
||||||
|
|
||||||
.PARAMETER ShareName
|
|
||||||
Name of the shared folder for FFU capture. The default is FFUCaptureShare. This share will be created with rights for the user account. When finished, the share will be removed.
|
|
||||||
|
|
||||||
.PARAMETER Username
|
|
||||||
Username for accessing the shared folder. The default is ffu_user. The script will auto create the account and password. When finished, it will remove the account.
|
|
||||||
|
|
||||||
.PARAMETER VMHostIPAddress
|
|
||||||
IP address of the Hyper-V host for FFU capture. If $InstallApps is set to $true, this parameter must be configured. You must manually configure this. The script will not auto detect your IP (depending on your network adapters, it may not find the correct IP).
|
|
||||||
|
|
||||||
.PARAMETER CreateCaptureMedia
|
|
||||||
When set to $true, this will create WinPE capture media for use when $InstallApps is set to $true. This capture media will be automatically attached to the VM and the boot order will be changed to automate the capture of the FFU.
|
|
||||||
|
|
||||||
.PARAMETER CreateDeploymentMedia
|
|
||||||
When set to $true, this will create WinPE deployment media for use when deploying to a physical device.
|
|
||||||
|
|
||||||
.PARAMETER OptionalFeatures
|
|
||||||
Provide a semi-colon separated list of Windows optional features you want to include in the FFU (e.g. netfx3;TFTP)
|
|
||||||
|
|
||||||
.PARAMETER ProductKey
|
|
||||||
Product key for the Windows 10/11 edition specified in WindowsSKU. This will overwrite whatever SKU is entered for WindowsSKU. Recommended to use if you want to use a MAK or KMS key to activate Enterprise or Education. If using VL media instead of consumer media, you'll want to enter a MAK or KMS key here.
|
|
||||||
|
|
||||||
.PARAMETER BuildUSBDrive
|
.PARAMETER BuildUSBDrive
|
||||||
When set to $true, will partition and format a USB drive and copy the captured FFU to the drive. If you'd like to customize the drive to add drivers, provisioning packages, name prefix, etc. You'll need to do that afterward.
|
When set to $true, will partition and format a USB drive and copy the captured FFU to the drive. If you'd like to customize the drive to add drivers, provisioning packages, name prefix, etc., you'll need to do that afterward.
|
||||||
|
|
||||||
.PARAMETER WindowsRelease
|
.PARAMETER CleanupAppsISO
|
||||||
Integer value of 10 or 11. This is used to identify which release of Windows to download. Default is 11.
|
When set to $true, will remove the Apps ISO after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
.PARAMETER WindowsVersion
|
.PARAMETER CleanupCaptureISO
|
||||||
String value of the Windows version to download. This is used to identify which version of Windows to download. Default is 23h2.
|
When set to $true, will remove the WinPE capture ISO after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
.PARAMETER WindowsArch
|
.PARAMETER CleanupDeployISO
|
||||||
String value of x86 or x64. This is used to identify which architecture of Windows to download. Default is x64.
|
When set to $true, will remove the WinPE deployment ISO after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
.PARAMETER WindowsLang
|
.PARAMETER CleanupDrivers
|
||||||
String value in language-region format (e.g. en-us). This is used to identify which language of media to download. Default is en-us.
|
When set to $true, will remove the drivers folder after the FFU has been captured. Default is $true.
|
||||||
|
|
||||||
.PARAMETER MediaType
|
.PARAMETER CompactOS
|
||||||
String value of either business or consumer. This is used to identify which media type to download. Default is consumer.
|
When set to $true, will compact the OS when building the FFU. Default is $true.
|
||||||
|
|
||||||
.PARAMETER LogicalSectorBytes
|
.PARAMETER ConfigFile
|
||||||
unit32 value of 512 or 4096. Not recommended to change from 512. Might be useful for 4kn drives, but needs more testing. Default is 512.
|
Path to a JSON file containing parameters to use for the script. Default is $null.
|
||||||
|
|
||||||
.PARAMETER Optimize
|
.PARAMETER CopyAutopilot
|
||||||
When set to $true, will optimize the FFU file. Default is $true.
|
When set to $true, will copy the $FFUDevelopmentPath\Autopilot folder to the Deployment partition of the USB drive. Default is $false.
|
||||||
|
|
||||||
.PARAMETER CopyDrivers
|
.PARAMETER CopyDrivers
|
||||||
When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers folder to the Drivers folder on the deploy partition of the USB drive. Default is $false.
|
When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers folder to the Drivers folder on the deploy partition of the USB drive. Default is $false.
|
||||||
@@ -100,9 +48,90 @@ When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers fo
|
|||||||
.PARAMETER CopyPEDrivers
|
.PARAMETER CopyPEDrivers
|
||||||
When set to $true, will copy the drivers from the $FFUDevelopmentPath\PEDrivers folder to the WinPE deployment media. Default is $false.
|
When set to $true, will copy the drivers from the $FFUDevelopmentPath\PEDrivers folder to the WinPE deployment media. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER CopyPPKG
|
||||||
|
When set to $true, will copy the provisioning package from the $FFUDevelopmentPath\PPKG folder to the Deployment partition of the USB drive. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER CopyUnattend
|
||||||
|
When set to $true, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER CreateCaptureMedia
|
||||||
|
When set to $true, this will create WinPE capture media for use when $InstallApps is set to $true. This capture media will be automatically attached to the VM, and the boot order will be changed to automate the capture of the FFU.
|
||||||
|
|
||||||
|
.PARAMETER CreateDeploymentMedia
|
||||||
|
When set to $true, this will create WinPE deployment media for use when deploying to a physical device.
|
||||||
|
|
||||||
|
.PARAMETER CustomFFUNameTemplate
|
||||||
|
Sets a custom FFU output name with placeholders. Allowed placeholders are: {Name}, {DisplayVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt}.
|
||||||
|
|
||||||
|
.PARAMETER Disksize
|
||||||
|
Size of the virtual hard disk for the virtual machine. Default is a 30GB dynamic disk.
|
||||||
|
|
||||||
|
.PARAMETER DriversFolder
|
||||||
|
Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers.
|
||||||
|
|
||||||
|
.PARAMETER FFUCaptureLocation
|
||||||
|
Path to the folder where the captured FFU will be stored. Default is $FFUDevelopmentPath\FFU.
|
||||||
|
|
||||||
|
.PARAMETER FFUDevelopmentPath
|
||||||
|
Path to the FFU development folder. Default is C:\FFUDevelopment.
|
||||||
|
|
||||||
|
.PARAMETER FFUPrefix
|
||||||
|
Prefix for the generated FFU file. Default is _FFU.
|
||||||
|
|
||||||
|
.PARAMETER Headers
|
||||||
|
Headers to use when downloading files. Not recommended to modify.
|
||||||
|
|
||||||
|
.PARAMETER InstallApps
|
||||||
|
When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created.
|
||||||
|
|
||||||
|
.PARAMETER InstallDrivers
|
||||||
|
Install device drivers from the specified $FFUDevelopmentPath\Drivers folder if set to $true. Download the drivers and put them in the Drivers folder. The script will recurse the drivers folder and add the drivers to the FFU.
|
||||||
|
|
||||||
|
.PARAMETER InstallOffice
|
||||||
|
Install Microsoft Office if set to $true. The script will download the latest ODT and Office files in the $FFUDevelopmentPath\Apps\Office folder and install Office in the FFU via VM.
|
||||||
|
|
||||||
|
.PARAMETER ISOPath
|
||||||
|
Path to the Windows 10/11 ISO file.
|
||||||
|
|
||||||
|
.PARAMETER LogicalSectorSizeBytes
|
||||||
|
Unit32 value of 512 or 4096. Useful for 4Kn drives or devices shipping with UFS drives. Default is 512.
|
||||||
|
|
||||||
|
.PARAMETER Make
|
||||||
|
Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell', 'HP', 'Lenovo'.
|
||||||
|
|
||||||
|
.PARAMETER MediaType
|
||||||
|
String value of either 'business' or 'consumer'. This is used to identify which media type to download. Default is 'consumer'.
|
||||||
|
|
||||||
|
.PARAMETER Memory
|
||||||
|
Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Default is 4GB.
|
||||||
|
|
||||||
|
.PARAMETER Model
|
||||||
|
Model of the device to download drivers. This is required if Make is set.
|
||||||
|
|
||||||
|
.PARAMETER Optimize
|
||||||
|
When set to $true, will optimize the FFU file. Default is $true.
|
||||||
|
|
||||||
|
.PARAMETER OptionalFeatures
|
||||||
|
Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP).
|
||||||
|
|
||||||
|
.PARAMETER Processors
|
||||||
|
Number of virtual processors for the virtual machine. Recommended to use at least 4.
|
||||||
|
|
||||||
|
.PARAMETER ProductKey
|
||||||
|
Product key for the Windows edition specified in WindowsSKU. This will overwrite whatever SKU is entered for WindowsSKU. Recommended to use if you want to use a MAK or KMS key to activate Enterprise or Education. If using VL media instead of consumer media, you'll want to enter a MAK or KMS key here.
|
||||||
|
|
||||||
|
.PARAMETER PromptExternalHardDiskMedia
|
||||||
|
When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true.
|
||||||
|
|
||||||
.PARAMETER RemoveFFU
|
.PARAMETER RemoveFFU
|
||||||
When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false.
|
When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER ShareName
|
||||||
|
Name of the shared folder for FFU capture. The default is FFUCaptureShare. This share will be created with rights for the user account. When finished, the share will be removed.
|
||||||
|
|
||||||
|
.PARAMETER UpdateEdge
|
||||||
|
When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateLatestCU
|
.PARAMETER UpdateLatestCU
|
||||||
When set to $true, will download and install the latest cumulative update for Windows 10/11. Default is $false.
|
When set to $true, will download and install the latest cumulative update for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
@@ -116,64 +145,46 @@ When set to $true, will download and install the latest .NET Framework for Windo
|
|||||||
When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false.
|
When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateLatestMSRT
|
.PARAMETER UpdateLatestMSRT
|
||||||
When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false
|
When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateEdge
|
.PARAMETER UpdateLatestNet
|
||||||
When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false.
|
When set to $true, will download and install the latest .NET Framework for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateOneDrive
|
.PARAMETER UpdateOneDrive
|
||||||
When set to $true, will download and install the latest OneDrive for Windows 10/11 and install it as a per machine installation instead of per user. Default is $false.
|
When set to $true, will download and install the latest OneDrive for Windows 10/11 and install it as a per-machine installation instead of per-user. Default is $false.
|
||||||
|
|
||||||
.PARAMETER CopyPPKG
|
.PARAMETER UpdatePreviewCU
|
||||||
When set to $true, will copy the provisioning package from the $FFUDevelopmentPath\PPKG folder to the Deployment partition of the USB drive. Default is $false.
|
When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
.PARAMETER CopyUnattend
|
|
||||||
When set to $true, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Default is $false.
|
|
||||||
|
|
||||||
.PARAMETER CopyAutopilot
|
|
||||||
When set to $true, will copy the $FFUDevelopmentPath\Autopilot folder to the Deployment partition of the USB drive. Default is $false.
|
|
||||||
|
|
||||||
.PARAMETER CompactOS
|
|
||||||
When set to $true, will compact the OS when building the FFU. Default is $true.
|
|
||||||
|
|
||||||
.PARAMETER CleanupCaptureISO
|
|
||||||
When set to $true, will remove the WinPE capture ISO after the FFU has been captured. Default is $true.
|
|
||||||
|
|
||||||
.PARAMETER CleanupDeployISO
|
|
||||||
When set to $true, will remove the WinPE deployment ISO after the FFU has been captured. Default is $true.
|
|
||||||
|
|
||||||
.PARAMETER CleanupAppsISO
|
|
||||||
When set to $true, will remove the Apps ISO after the FFU has been captured. Default is $true.
|
|
||||||
|
|
||||||
.PARAMETER DriversFolder
|
|
||||||
Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers.
|
|
||||||
|
|
||||||
.PARAMETER CleanupDrivers
|
|
||||||
When set to $true, will remove the drivers folder after the FFU has been captured. Default is $true.
|
|
||||||
|
|
||||||
.PARAMETER UserAgent
|
.PARAMETER UserAgent
|
||||||
User agent string to use when downloading files.
|
User agent string to use when downloading files.
|
||||||
|
|
||||||
.PARAMETER Headers
|
.PARAMETER Username
|
||||||
Headers to use when downloading files.
|
Username for accessing the shared folder. The default is ffu_user. The script will auto-create the account and password. When finished, it will remove the account.
|
||||||
|
|
||||||
.PARAMETER AllowExternalHardDiskMedia
|
.PARAMETER VMHostIPAddress
|
||||||
When set to $true, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined.
|
IP address of the Hyper-V host for FFU capture. If $InstallApps is set to $true, this parameter must be configured. You must manually configure this. The script will not auto-detect your IP (depending on your network adapters, it may not find the correct IP).
|
||||||
|
|
||||||
.PARAMETER PromptExternalHardDiskMedia
|
.PARAMETER VMLocation
|
||||||
When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true.
|
Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to.
|
||||||
|
|
||||||
.PARAMETER Make
|
.PARAMETER VMSwitchName
|
||||||
Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell', 'HP', 'Lenovo'
|
Name of the Hyper-V virtual switch. If $InstallApps is set to $true, this must be set. This is required to capture the FFU from the VM. The default is '*external*', but you will likely need to change this.
|
||||||
|
|
||||||
.PARAMETER Model
|
.PARAMETER WindowsArch
|
||||||
Model of the device to download drivers. This is required if Make is set.
|
String value of 'x86' or 'x64'. This is used to identify which architecture of Windows to download. Default is 'x64'.
|
||||||
|
|
||||||
.PARAMETER AppsScriptVariables
|
.PARAMETER WindowsLang
|
||||||
When passed a hashtable, the script will alter the $FFUDevelopmentPath\Apps\InstallAppsandSysprep.cmd file to set variables with the hashtable keys as variable names and the hashtable values their content.
|
String value in language-region format (e.g., 'en-us'). This is used to identify which language of media to download. Default is 'en-us'.
|
||||||
|
|
||||||
.PARAMETER CustomFFUNameTemplate
|
.PARAMETER WindowsRelease
|
||||||
Sets a custom FFU output name with placeholders. Allowed placeholders are: {Name}, {DisplayVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt}
|
Integer value of 10 or 11. This is used to identify which release of Windows to download. Default is 11.
|
||||||
|
|
||||||
|
.PARAMETER WindowsSKU
|
||||||
|
Edition of Windows 10/11 to be installed. Accepted values are: 'Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N'.
|
||||||
|
|
||||||
|
.PARAMETER WindowsVersion
|
||||||
|
String value of the Windows version to download. This is used to identify which version of Windows to download. Default is '23h2'.
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates.
|
Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates.
|
||||||
@@ -222,16 +233,6 @@ param(
|
|||||||
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
|
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
|
||||||
[string]$Make,
|
[string]$Make,
|
||||||
[string]$Model,
|
[string]$Model,
|
||||||
# [Parameter(Mandatory = $false)]
|
|
||||||
# [ValidateScript({
|
|
||||||
# if ($Make) {
|
|
||||||
# return $true
|
|
||||||
# }
|
|
||||||
# if ($_ -and (!(Test-Path -Path '.\Drivers') -or ((Get-ChildItem -Path '.\Drivers' -Recurse | Measure-Object -Property Length -Sum).Sum -lt 1MB))) {
|
|
||||||
# throw 'InstallDrivers is set to $true, but either the Drivers folder is missing or empty'
|
|
||||||
# }
|
|
||||||
# return $true
|
|
||||||
# })]
|
|
||||||
[bool]$InstallDrivers,
|
[bool]$InstallDrivers,
|
||||||
[uint64]$Memory = 4GB,
|
[uint64]$Memory = 4GB,
|
||||||
[uint64]$Disksize = 30GB,
|
[uint64]$Disksize = 30GB,
|
||||||
@@ -331,6 +332,7 @@ param(
|
|||||||
[bool]$CleanupDeployISO = $true,
|
[bool]$CleanupDeployISO = $true,
|
||||||
[bool]$CleanupAppsISO = $true,
|
[bool]$CleanupAppsISO = $true,
|
||||||
[string]$DriversFolder,
|
[string]$DriversFolder,
|
||||||
|
[string]$PEDriversFolder,
|
||||||
[bool]$CleanupDrivers = $true,
|
[bool]$CleanupDrivers = $true,
|
||||||
[string]$UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0',
|
[string]$UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0',
|
||||||
#Microsoft sites will intermittently fail on downloads. These headers are to help with that.
|
#Microsoft sites will intermittently fail on downloads. These headers are to help with that.
|
||||||
@@ -349,9 +351,51 @@ param(
|
|||||||
"Upgrade-Insecure-Requests" = "1"
|
"Upgrade-Insecure-Requests" = "1"
|
||||||
},
|
},
|
||||||
[bool]$AllowExternalHardDiskMedia,
|
[bool]$AllowExternalHardDiskMedia,
|
||||||
[bool]$PromptExternalHardDiskMedia = $true
|
[bool]$PromptExternalHardDiskMedia = $true,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[ValidateScript({ $_ -eq $null -or (Test-Path $_) })]
|
||||||
|
[string]$ConfigFile,
|
||||||
|
[Parameter(Mandatory = $false)]
|
||||||
|
[string]$ExportConfigFile
|
||||||
)
|
)
|
||||||
$version = '2410.1'
|
$version = '2412.1'
|
||||||
|
|
||||||
|
# If a config file is specified and it exists, load it
|
||||||
|
if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
|
||||||
|
$configData = Get-Content $ConfigFile -Raw | ConvertFrom-Json
|
||||||
|
$keys = $configData.psobject.Properties.Name
|
||||||
|
|
||||||
|
# Iterate through the keys in the config data
|
||||||
|
foreach ($key in $keys) {
|
||||||
|
$value = $configdata.$key
|
||||||
|
|
||||||
|
# If $value is empty, skip
|
||||||
|
if ($null -eq $value -or
|
||||||
|
([string]::IsNullOrEmpty([string]$value)) -or
|
||||||
|
($value -is [System.Collections.Hashtable] -and $value.Count -eq 0) -or
|
||||||
|
($value -is [System.UInt32] -and $value -eq 0) -or
|
||||||
|
($value -is [System.UInt64] -and $value -eq 0) -or
|
||||||
|
($value -is [System.Int32] -and $value -eq 0)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# If this is the Headers parameter, convert PSCustomObject to hashtable
|
||||||
|
if ($key -eq 'Headers' -and $value -is [System.Management.Automation.PSCustomObject]) {
|
||||||
|
$headers = [hashtable]::new()
|
||||||
|
foreach ($prop in $value.psobject.Properties) {
|
||||||
|
$headers[$prop.Name] = $prop.Value
|
||||||
|
}
|
||||||
|
$value = $headers
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if this key matches a parameter in the script
|
||||||
|
# and if the user did NOT explicitly supply it on the command line
|
||||||
|
if ($MyInvocation.MyCommand.Parameters.ContainsKey($key) -and -not $PSBoundParameters.ContainsKey($key)) {
|
||||||
|
# Set the parameter's value to what's in the config file
|
||||||
|
Set-Variable -Name $key -Value $value -Scope 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#Class definition for vhdx cache
|
#Class definition for vhdx cache
|
||||||
class VhdxCacheUpdateItem {
|
class VhdxCacheUpdateItem {
|
||||||
@@ -363,6 +407,7 @@ class VhdxCacheUpdateItem {
|
|||||||
|
|
||||||
class VhdxCacheItem {
|
class VhdxCacheItem {
|
||||||
[string]$VhdxFileName = ""
|
[string]$VhdxFileName = ""
|
||||||
|
[uint32]$LogicalSectorSizeBytes = ""
|
||||||
[string]$WindowsSKU = ""
|
[string]$WindowsSKU = ""
|
||||||
[string]$WindowsRelease = ""
|
[string]$WindowsRelease = ""
|
||||||
[string]$WindowsVersion = ""
|
[string]$WindowsVersion = ""
|
||||||
@@ -430,6 +475,20 @@ function WriteLog($LogText) {
|
|||||||
Write-Verbose $LogText
|
Write-Verbose $LogText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-Parameters{
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter()]
|
||||||
|
$ParamNames
|
||||||
|
)
|
||||||
|
# Define unwanted parameters
|
||||||
|
$excludedParams = 'Debug','ErrorAction','ErrorVariable','InformationAction','InformationVariable','OutBuffer','OutVariable','PipelineVariable','Verbose','WarningAction','WarningVariable'
|
||||||
|
|
||||||
|
# Filter out the unwanted parameters
|
||||||
|
$filteredParamNames = $paramNames | Where-Object { $excludedParams -notcontains $_ }
|
||||||
|
return $filteredParamNames
|
||||||
|
}
|
||||||
|
|
||||||
function LogVariableValues {
|
function LogVariableValues {
|
||||||
$excludedVariables = @(
|
$excludedVariables = @(
|
||||||
'PSBoundParameters',
|
'PSBoundParameters',
|
||||||
@@ -614,41 +673,66 @@ function Get-MicrosoftDrivers {
|
|||||||
$VerbosePreference = $OriginalVerbosePreference
|
$VerbosePreference = $OriginalVerbosePreference
|
||||||
WriteLog "Complete"
|
WriteLog "Complete"
|
||||||
|
|
||||||
# Parse the HTML content
|
# Parse the HTML content using Regex instead of the HTMLFILE COM object
|
||||||
WriteLog "Parsing web content for models and download links"
|
WriteLog "Parsing web content for models and download links"
|
||||||
$html = $webContent.Content
|
$html = $webContent.Content
|
||||||
$document = New-Object -ComObject "HTMLFILE"
|
|
||||||
$document.IHTMLDocument2_write($html)
|
# Regex to match divs with selectable-content-options__option-content classes
|
||||||
$document.Close()
|
$divPattern = '<div[^>]*class="selectable-content-options__option-content(?: ocHidden)?"[^>]*>(.*?)</div>'
|
||||||
|
$divMatches = [regex]::Matches($html, $divPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
||||||
|
|
||||||
$models = @()
|
$models = @()
|
||||||
|
|
||||||
# Select all divs with class 'selectable-content-options__option-content'
|
foreach ($divMatch in $divMatches) {
|
||||||
$divs = $document.getElementsByTagName("div") | Where-Object {
|
$divContent = $divMatch.Groups[1].Value
|
||||||
$_.className -eq "selectable-content-options__option-content" -or $_.className -eq "selectable-content-options__option-content ocHidden"
|
|
||||||
|
# Find all tables within the div
|
||||||
|
$tablePattern = '<table[^>]*>(.*?)</table>'
|
||||||
|
$tableMatches = [regex]::Matches($divContent, $tablePattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
||||||
|
|
||||||
|
foreach ($tableMatch in $tableMatches) {
|
||||||
|
$tableContent = $tableMatch.Groups[1].Value
|
||||||
|
|
||||||
|
# Find all rows in the table
|
||||||
|
$rowPattern = '<tr[^>]*>(.*?)</tr>'
|
||||||
|
$rowMatches = [regex]::Matches($tableContent, $rowPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
||||||
|
|
||||||
|
foreach ($rowMatch in $rowMatches) {
|
||||||
|
$rowContent = $rowMatch.Groups[1].Value
|
||||||
|
|
||||||
|
# Extract cells from the row
|
||||||
|
$cellPattern = '<td[^>]*>\s*(?:<p[^>]*>)?(.*?)(?:</p>)?\s*</td>'
|
||||||
|
$cellMatches = [regex]::Matches($rowContent, $cellPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
||||||
|
|
||||||
|
if ($cellMatches.Count -ge 2) {
|
||||||
|
# Model name in the first TD
|
||||||
|
$modelName = ($cellMatches[0].Groups[1].Value).Trim()
|
||||||
|
|
||||||
|
# # Remove <p> and </p> tags if present
|
||||||
|
# $modelName = $modelName -replace '<p[^>]*>', '' -replace '</p>', ''
|
||||||
|
# $modelName = $modelName.Trim()
|
||||||
|
|
||||||
|
|
||||||
|
# The second TD might contain a link or just text
|
||||||
|
$secondTdContent = $cellMatches[1].Groups[1].Value.Trim()
|
||||||
|
|
||||||
|
# Look for a link in the second TD
|
||||||
|
$linkPattern = '<a[^>]+href="([^"]+)"[^>]*>'
|
||||||
|
$linkMatch = [regex]::Match($secondTdContent, $linkPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
||||||
|
|
||||||
|
if ($linkMatch.Success) {
|
||||||
|
$modelLink = $linkMatch.Groups[1].Value
|
||||||
|
} else {
|
||||||
|
# No link, just text instructions
|
||||||
|
$modelLink = $secondTdContent
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($div in $divs) {
|
|
||||||
$tables = $div.getElementsByTagName("table")
|
|
||||||
foreach ($table in $tables) {
|
|
||||||
$rows = $table.getElementsByTagName("tr")
|
|
||||||
foreach ($row in $rows) {
|
|
||||||
$cells = $row.getElementsByTagName("td")
|
|
||||||
if ($cells.length -ge 2) {
|
|
||||||
$modelName = $cells[0].innerText.Trim()
|
|
||||||
$linkElement = $cells[1].getElementsByTagName("a")
|
|
||||||
if ($linkElement.length -gt 0) {
|
|
||||||
$modelLink = $linkElement[0].href
|
|
||||||
$models += [PSCustomObject]@{ Model = $modelName; Link = $modelLink }
|
|
||||||
} else {
|
|
||||||
# Handle cases with instructions instead of links
|
|
||||||
$modelLink = $cells[1].innerText.Trim()
|
|
||||||
$models += [PSCustomObject]@{ Model = $modelName; Link = $modelLink }
|
$models += [PSCustomObject]@{ Model = $modelName; Link = $modelLink }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
WriteLog "Parsing complete"
|
WriteLog "Parsing complete"
|
||||||
|
|
||||||
# Validate the model
|
# Validate the model
|
||||||
@@ -696,7 +780,7 @@ function Get-MicrosoftDrivers {
|
|||||||
$VerbosePreference = $OriginalVerbosePreference
|
$VerbosePreference = $OriginalVerbosePreference
|
||||||
WriteLog "Complete"
|
WriteLog "Complete"
|
||||||
WriteLog "Parsing download page for file"
|
WriteLog "Parsing download page for file"
|
||||||
$scriptPattern = '<script>window.__DLCDetails__={(.*?)}</script>'
|
$scriptPattern = '<script>window.__DLCDetails__={(.*?)}<\/script>'
|
||||||
$scriptMatch = [regex]::Match($downloadPageContent.Content, $scriptPattern)
|
$scriptMatch = [regex]::Match($downloadPageContent.Content, $scriptPattern)
|
||||||
|
|
||||||
if ($scriptMatch.Success) {
|
if ($scriptMatch.Success) {
|
||||||
@@ -1369,7 +1453,7 @@ function Get-DellDrivers {
|
|||||||
# New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
# New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
||||||
# WriteLog "Extraction folder created"
|
# WriteLog "Extraction folder created"
|
||||||
|
|
||||||
# $arguments = "/s /e=`"$extractFolder`""
|
# $arguments = "/s /e /f `"$extractFolder`""
|
||||||
$arguments = "/s /drivers=`"$extractFolder`""
|
$arguments = "/s /drivers=`"$extractFolder`""
|
||||||
WriteLog "Extracting driver: $driverFilePath $arguments"
|
WriteLog "Extracting driver: $driverFilePath $arguments"
|
||||||
try {
|
try {
|
||||||
@@ -2344,7 +2428,7 @@ function Get-KBLink {
|
|||||||
Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } |
|
Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } |
|
||||||
Select-Object -ExpandProperty ID
|
Select-Object -ExpandProperty ID
|
||||||
|
|
||||||
Write-Verbose -Message "$kbids"
|
# Write-Verbose -Message "$kbids"
|
||||||
|
|
||||||
if (-not $kbids) {
|
if (-not $kbids) {
|
||||||
Write-Warning -Message "No results found for $Name"
|
Write-Warning -Message "No results found for $Name"
|
||||||
@@ -2442,34 +2526,7 @@ function Save-KB {
|
|||||||
foreach ($link in $links) {
|
foreach ($link in $links) {
|
||||||
if (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
|
if (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
|
||||||
WriteLog "No architecture found in $link, skipping"
|
WriteLog "No architecture found in $link, skipping"
|
||||||
#FIX: 3/22/2024 - the SecurityHealthSetup fix was updated and now includes two files (one is x64 and the other is arm64)
|
continue
|
||||||
#Unfortunately there is no easy way to determine the architecture from the file name
|
|
||||||
#There is a support doc that include links to download, but it's out of date (n-1)
|
|
||||||
#https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
|
|
||||||
#These files don't change that often, so will check the link above to see when it updates and may use that
|
|
||||||
#For now this is hard-coded for these specific file names
|
|
||||||
# if ($link -match 'security') {
|
|
||||||
# #Make sure we're getting the correct architecture for the Security Health Setup update
|
|
||||||
# WriteLog "Link: $link matches security"
|
|
||||||
# if ($WindowsArch -eq 'x64') {
|
|
||||||
# if ($link -match 'securityhealthsetup_e1') {
|
|
||||||
# Writelog "Downloading $Link for $WindowsArch to $Path"
|
|
||||||
# Start-BitsTransferWithRetry -Source $link -Destination $Path
|
|
||||||
# $fileName = ($link -split '/')[-1]
|
|
||||||
# Writelog "Returning $fileName"
|
|
||||||
# break
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# if ($WindowsArch -eq 'arm64') {
|
|
||||||
# if ($link -match 'securityhealthsetup_25') {
|
|
||||||
# Writelog "Downloading $Link for $WindowsArch to $Path"
|
|
||||||
# Start-BitsTransferWithRetry -Source $link -Destination $Path
|
|
||||||
# $fileName = ($link -split '/')[-1]
|
|
||||||
# Writelog "Returning $fileName"
|
|
||||||
# break
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($link -match 'x64' -or $link -match 'amd64') {
|
if ($link -match 'x64' -or $link -match 'amd64') {
|
||||||
@@ -2503,7 +2560,8 @@ function Save-KB {
|
|||||||
function New-AppsISO {
|
function New-AppsISO {
|
||||||
#Create Apps ISO file
|
#Create Apps ISO file
|
||||||
$OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
|
$OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
|
||||||
#Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m -d $Appspath $AppsISO" -wait
|
#Adding Long Path support for AppsPath to prevent issues with oscdimg
|
||||||
|
$AppsPath = '\\?\' + $AppsPath
|
||||||
Invoke-Process $OSCDIMG "-n -m -d $Appspath $AppsISO" | Out-Null
|
Invoke-Process $OSCDIMG "-n -m -d $Appspath $AppsISO" | Out-Null
|
||||||
|
|
||||||
#Remove the Office Download and ODT
|
#Remove the Office Download and ODT
|
||||||
@@ -2901,15 +2959,14 @@ Function Set-CaptureFFU {
|
|||||||
$ScriptContent = Get-Content -Path $CaptureFFUScriptPath
|
$ScriptContent = Get-Content -Path $CaptureFFUScriptPath
|
||||||
$UpdatedContent = $ScriptContent -replace '(net use).*', ("$SharePath")
|
$UpdatedContent = $ScriptContent -replace '(net use).*', ("$SharePath")
|
||||||
WriteLog 'Updating share command in CaptureFFU.ps1 script with new share information'
|
WriteLog 'Updating share command in CaptureFFU.ps1 script with new share information'
|
||||||
$UpdatedContent = $UpdatedContent -replace '^\$CustomFFUNameTemplate \= .*#Custom naming', "#Custom naming placeholder"
|
$UpdatedContent = $UpdatedContent -replace '^\$CustomFFUNameTemplate \= .*#Custom naming', '#Custom naming placeholder'
|
||||||
if (![string]::IsNullOrEmpty($CustomFFUNameTemplate)) {
|
if (![string]::IsNullOrEmpty($CustomFFUNameTemplate)) {
|
||||||
$UpdatedContent = $UpdatedContent -replace '#Custom naming placeholder', ("`$CustomFFUNameTemplate = '$CustomFFUNameTemplate' #Custom naming")
|
$UpdatedContent = $UpdatedContent -replace '#Custom naming placeholder', ("`$CustomFFUNameTemplate = '$CustomFFUNameTemplate' #Custom naming")
|
||||||
WriteLog 'Updating share command in CaptureFFU.ps1 script with new ffu name template information'
|
WriteLog 'Updating CaptureFFU.ps1 script with new ffu name template information'
|
||||||
}
|
}
|
||||||
Set-Content -Path $CaptureFFUScriptPath -Value $UpdatedContent
|
Set-Content -Path $CaptureFFUScriptPath -Value $UpdatedContent
|
||||||
WriteLog 'Update complete'
|
WriteLog 'Update complete'
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throw "CaptureFFU.ps1 script not found at $CaptureFFUScriptPath"
|
throw "CaptureFFU.ps1 script not found at $CaptureFFUScriptPath"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2991,7 +3048,7 @@ function New-PEMedia {
|
|||||||
if ($CopyPEDrivers) {
|
if ($CopyPEDrivers) {
|
||||||
WriteLog "Adding drivers to WinPE media"
|
WriteLog "Adding drivers to WinPE media"
|
||||||
try {
|
try {
|
||||||
Add-WindowsDriver -Path "$WinPEFFUPath\Mount" -Driver "$FFUDevelopmentPath\PEDrivers" -Recurse -ErrorAction SilentlyContinue | Out-null
|
Add-WindowsDriver -Path "$WinPEFFUPath\Mount" -Driver "$FFUDevelopmentPath\$PEDriversFolder" -Recurse -ErrorAction SilentlyContinue | Out-null
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog 'Some drivers failed to be added to the FFU. This can be expected. Continuing.'
|
WriteLog 'Some drivers failed to be added to the FFU. This can be expected. Continuing.'
|
||||||
@@ -3064,6 +3121,35 @@ function Optimize-FFUCaptureDrive {
|
|||||||
throw $_
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function New-FFUFileName {
|
||||||
|
$BuildDate = Get-Date -uformat %b%Y
|
||||||
|
# Replace '{WindowsRelease}' with the Windows release (e.g., 10/11 or server)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsRelease}', $WindowsRelease
|
||||||
|
# Replace '{WindowsVersion}' with the Windows version (e.g., 24h2)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsVersion}', $WindowsVersion
|
||||||
|
# Replace '{SKU}' with the SKU of the Windows image (e.g., Pro, Enterprise, etc.)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{SKU}', $SKU
|
||||||
|
# Replace '{BuildDate}' with the current month and year (e.g., Jan2023)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{BuildDate}', $BuildDate
|
||||||
|
# Replace '{yyyy}' with the current year in 4-digit format (e.g., 2023)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{yyyy}', (Get-Date -UFormat '%Y')
|
||||||
|
# Replace '{MM}' with the current month in 2-digit format (e.g., 01 for January)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{MM}', (Get-Date -UFormat '%m')
|
||||||
|
# Replace '{dd}' with the current day of the month in 2-digit format (e.g., 05)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{dd}', (Get-Date -UFormat '%d')
|
||||||
|
# Replace '{HH}' with the current hour in 24-hour format (e.g., 14 for 2 PM)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{HH}', (Get-Date -UFormat '%H')
|
||||||
|
# Replace '{hh}' with the current hour in 12-hour format (e.g., 02 for 2 PM)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{hh}', (Get-Date -UFormat '%I')
|
||||||
|
# Replace '{mm}' with the current minute in 2-digit format (e.g., 09)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{mm}', (Get-Date -UFormat '%M')
|
||||||
|
# Replace '{tt}' with the current AM/PM designator (e.g., AM or PM)
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{tt}', (Get-Date -UFormat '%p')
|
||||||
|
if($CustomFFUNameTemplate -notlike '*.ffu') {
|
||||||
|
$CustomFFUNameTemplate += '.ffu'
|
||||||
|
}
|
||||||
|
return $CustomFFUNameTemplate
|
||||||
|
}
|
||||||
|
|
||||||
function New-FFU {
|
function New-FFU {
|
||||||
param (
|
param (
|
||||||
@@ -3107,10 +3193,15 @@ function New-FFU {
|
|||||||
throw $_
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif (-not $InstallApps) {
|
elseif (-not $InstallApps -and (-not $AllowVHDXCaching)) {
|
||||||
|
if ($CustomFFUNameTemplate) {
|
||||||
|
$FFUFileName = New-FFUFileName
|
||||||
|
}
|
||||||
|
else{
|
||||||
#Get Windows Version Information from the VHDX
|
#Get Windows Version Information from the VHDX
|
||||||
$winverinfo = Get-WindowsVersionInfo
|
$winverinfo = Get-WindowsVersionInfo
|
||||||
$FFUFileName = "$($winverinfo.Name)`_$($winverinfo.DisplayVersion)`_$($winverinfo.SKU)`_$($winverinfo.BuildDate).ffu"
|
$FFUFileName = "$($winverinfo.Name)`_$($winverinfo.DisplayVersion)`_$($winverinfo.SKU)`_$($winverinfo.BuildDate).ffu"
|
||||||
|
}
|
||||||
WriteLog "FFU file name: $FFUFileName"
|
WriteLog "FFU file name: $FFUFileName"
|
||||||
$FFUFile = "$FFUCaptureLocation\$FFUFileName"
|
$FFUFile = "$FFUCaptureLocation\$FFUFileName"
|
||||||
#Capture the FFU
|
#Capture the FFU
|
||||||
@@ -3119,6 +3210,29 @@ function New-FFU {
|
|||||||
WriteLog 'FFU Capture complete'
|
WriteLog 'FFU Capture complete'
|
||||||
Dismount-ScratchVhdx -VhdxPath $VHDXPath
|
Dismount-ScratchVhdx -VhdxPath $VHDXPath
|
||||||
}
|
}
|
||||||
|
elseif (-not $InstallApps -and $AllowVHDXCaching) {
|
||||||
|
# Make $FFUFileName based on values in the config.json file
|
||||||
|
if ($CustomFFUNameTemplate) {
|
||||||
|
$FFUFileName = New-FFUFileName
|
||||||
|
} else {
|
||||||
|
$BuildDate = Get-Date -UFormat %b%Y
|
||||||
|
# Get Windows Information to make the FFU file name from the cachedVHDXInfo file
|
||||||
|
if ($installationType -eq 'Client') {
|
||||||
|
$FFUFileName = "Win$($cachedVHDXInfo.WindowsRelease)`_$($cachedVHDXInfo.WindowsVersion)`_$($cachedVHDXInfo.WindowsSKU)`_$BuildDate.ffu"
|
||||||
|
} else {
|
||||||
|
$FFUFileName = "Server$($cachedVHDXInfo.WindowsRelease)`_$($cachedVHDXInfo.WindowsVersion)`_$($cachedVHDXInfo.WindowsSKU)`_$BuildDate.ffu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WriteLog "FFU file name: $FFUFileName"
|
||||||
|
$FFUFile = "$FFUCaptureLocation\$FFUFileName"
|
||||||
|
#Dismount the VHDX
|
||||||
|
|
||||||
|
#Capture the FFU
|
||||||
|
Invoke-Process cmd "/c ""$DandIEnv"" && dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($cachedVHDXInfo.WindowsRelease)$($cachedVHDXInfo.WindowsVersion)$($cachedVHDXInfo.WindowsSKU) /Compress:Default" | Out-Null
|
||||||
|
# Invoke-Process cmd "/c dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($winverinfo.Name)$($winverinfo.DisplayVersion)$($winverinfo.SKU) /Compress:Default" | Out-Null
|
||||||
|
WriteLog 'FFU Capture complete'
|
||||||
|
Dismount-ScratchVhdx -VhdxPath $VHDXPath
|
||||||
|
}
|
||||||
|
|
||||||
#Without this 120 second sleep, we sometimes see an error when mounting the FFU due to a file handle lock. Needed for both driver and optimize steps.
|
#Without this 120 second sleep, we sometimes see an error when mounting the FFU due to a file handle lock. Needed for both driver and optimize steps.
|
||||||
WriteLog 'Sleeping 2 minutes to prevent file handle lock'
|
WriteLog 'Sleeping 2 minutes to prevent file handle lock'
|
||||||
@@ -3233,7 +3347,7 @@ Function Get-WindowsVersionInfo {
|
|||||||
WriteLog "Getting Windows Version info"
|
WriteLog "Getting Windows Version info"
|
||||||
#Load Registry Hive
|
#Load Registry Hive
|
||||||
$Software = "$osPartitionDriveLetter`:\Windows\System32\config\software"
|
$Software = "$osPartitionDriveLetter`:\Windows\System32\config\software"
|
||||||
WriteLog "Loading Software registry hive"
|
WriteLog "Loading Software registry hive: $Software"
|
||||||
Invoke-Process reg "load HKLM\FFU $Software" | Out-Null
|
Invoke-Process reg "load HKLM\FFU $Software" | Out-Null
|
||||||
|
|
||||||
#Find Windows version values
|
#Find Windows version values
|
||||||
@@ -3824,9 +3938,33 @@ function Clear-InstallAppsandSysprep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function Export-ConfigFile{
|
||||||
|
[CmdletBinding()]
|
||||||
|
param (
|
||||||
|
[Parameter()]
|
||||||
|
$paramNames
|
||||||
|
)
|
||||||
|
$filteredParamNames = Get-Parameters -ParamNames $paramNames
|
||||||
|
|
||||||
|
# Retrieve their values
|
||||||
|
$paramsToExport = @{}
|
||||||
|
foreach ($paramName in $filteredParamNames) {
|
||||||
|
$paramsToExport[$paramName] = Get-Variable -Name $paramName -ValueOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort the keys alphabetically
|
||||||
|
$orderedParams = [ordered]@{}
|
||||||
|
foreach ($key in ($paramsToExport.Keys | Sort-Object)) {
|
||||||
|
$orderedParams[$key] = $paramsToExport[$key]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert to JSON and save
|
||||||
|
$orderedParams | ConvertTo-Json | Out-File $ExportConfigFile -Force
|
||||||
|
}
|
||||||
|
|
||||||
###END FUNCTIONS
|
###END FUNCTIONS
|
||||||
|
|
||||||
|
|
||||||
#Remove old log file if found
|
#Remove old log file if found
|
||||||
if (Test-Path -Path $Logfile) {
|
if (Test-Path -Path $Logfile) {
|
||||||
Remove-item -Path $LogFile -Force
|
Remove-item -Path $LogFile -Force
|
||||||
@@ -3838,11 +3976,48 @@ Write-Host "To track progress, please open the log file $Logfile or use the -Ver
|
|||||||
|
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
|
|
||||||
|
####### Generate Config File #######
|
||||||
|
|
||||||
|
if($ExportConfigFile){
|
||||||
|
WriteLog 'Exporting Config File'
|
||||||
|
# Get the parameter names from the script and exclude ExportConfigFile
|
||||||
|
$paramNames = $MyInvocation.MyCommand.Parameters.Keys | Where-Object {$_ -ne 'ExportConfigFile'}
|
||||||
|
try{
|
||||||
|
Export-ConfigFile($paramNames)
|
||||||
|
WriteLog "Config file exported to $ExportConfigFile"
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
WriteLog 'Failed to export config file'
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
####### End Generate Config File #######
|
||||||
|
|
||||||
|
|
||||||
|
#Setting long path support - this prevents issues where some applications have deep directory structures
|
||||||
|
#and oscdimg fails to create the Apps ISO
|
||||||
|
try {
|
||||||
|
$LongPathsEnabled = Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -ErrorAction Stop
|
||||||
|
} catch {
|
||||||
|
$LongPathsEnabled = $null
|
||||||
|
}
|
||||||
|
if ($LongPathsEnabled -ne 1) {
|
||||||
|
WriteLog 'LongPathsEnabled is not set to 1. Setting it to 1'
|
||||||
|
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
||||||
|
WriteLog 'LongPathsEnabled set to 1'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
###PARAMETER VALIDATION
|
###PARAMETER VALIDATION
|
||||||
|
|
||||||
#Validate drivers folder
|
#Validate drivers folder
|
||||||
if ($InstallDrivers -or $CopyDrivers) {
|
if ($InstallDrivers -or $CopyDrivers) {
|
||||||
WriteLog 'Doing driver validation'
|
WriteLog 'Doing driver validation'
|
||||||
|
if ($DriversFolder -match '\s') {
|
||||||
|
WriteLog "Driver folder path $DriversFolder contains spaces. Please remove spaces from the path and try again."
|
||||||
|
throw "Driver folder path $DriversFolder contains spaces. Please remove spaces from the path and try again."
|
||||||
|
}
|
||||||
if ($Make -and $Model){
|
if ($Make -and $Model){
|
||||||
WriteLog "Make and Model are set to $Make and $Model, will attempt to download drivers"
|
WriteLog "Make and Model are set to $Make and $Model, will attempt to download drivers"
|
||||||
} else {
|
} else {
|
||||||
@@ -3860,6 +4035,11 @@ if ($InstallDrivers -or $CopyDrivers) {
|
|||||||
#Validate PEDrivers folder
|
#Validate PEDrivers folder
|
||||||
if ($CopyPEDrivers) {
|
if ($CopyPEDrivers) {
|
||||||
WriteLog 'Doing PEDriver validation'
|
WriteLog 'Doing PEDriver validation'
|
||||||
|
# Check if $PEdriversFolder has spaces in the path, if it does, throw an error
|
||||||
|
if ($PEDriversFolder -match '\s') {
|
||||||
|
WriteLog "Driver folder path $PEDriversFolder contains spaces. Please remove spaces from the path and try again."
|
||||||
|
throw "Driver folder path $PEDriversFolder contains spaces. Please remove spaces from the path and try again."
|
||||||
|
}
|
||||||
if (!(Test-Path -Path $PEDriversFolder)) {
|
if (!(Test-Path -Path $PEDriversFolder)) {
|
||||||
WriteLog "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
|
WriteLog "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
|
||||||
throw "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
|
throw "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
|
||||||
@@ -4115,21 +4295,6 @@ if ($InstallApps) {
|
|||||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||||
WriteLog "Update complete"
|
WriteLog "Update complete"
|
||||||
|
|
||||||
###### 9/4/2024 - Windows Security Platform update is no longer available from Update Catalog. Will change to using
|
|
||||||
###### https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
|
|
||||||
|
|
||||||
# #Get Windows Security platform update
|
|
||||||
# $Name = "Windows Security platform definition updates"
|
|
||||||
# WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $DefenderPath"
|
|
||||||
# $KBFilePath = Save-KB -Name $Name -Path $DefenderPath
|
|
||||||
# WriteLog "Latest Security Platform Update saved to $DefenderPath\$KBFilePath"
|
|
||||||
# #Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update
|
|
||||||
# WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update"
|
|
||||||
# $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
|
||||||
# $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Security Platform Update)', ("REM Install Windows Security Platform Update`r`nd:\Defender\$KBFilePath")
|
|
||||||
# Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
|
||||||
# WriteLog "Update complete"
|
|
||||||
|
|
||||||
#Download latest Defender Definitions
|
#Download latest Defender Definitions
|
||||||
WriteLog "Downloading latest Defender Definitions"
|
WriteLog "Downloading latest Defender Definitions"
|
||||||
# Defender def updates can be found https://www.microsoft.com/en-us/wdsi/defenderupdates
|
# Defender def updates can be found https://www.microsoft.com/en-us/wdsi/defenderupdates
|
||||||
@@ -4156,6 +4321,34 @@ if ($InstallApps) {
|
|||||||
$UpdatedcmdContent = $CmdContent -replace '^(REM Install Defender Definitions)', ("REM Install Defender Definitions`r`nd:\Defender\mpam-fe.exe")
|
$UpdatedcmdContent = $CmdContent -replace '^(REM Install Defender Definitions)', ("REM Install Defender Definitions`r`nd:\Defender\mpam-fe.exe")
|
||||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||||
WriteLog "Update complete"
|
WriteLog "Update complete"
|
||||||
|
|
||||||
|
###### 9/4/2024 - Windows Security Platform update is no longer available from Update Catalog. Will change to using
|
||||||
|
###### https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
|
||||||
|
|
||||||
|
#Download Windows Security Platform Update
|
||||||
|
WriteLog "Downloading Windows Security Platform Update"
|
||||||
|
if ($WindowsArch -eq 'x64') {
|
||||||
|
$securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/x64/securityhealthsetup.exe'
|
||||||
|
}
|
||||||
|
if ($WindowsArch -eq 'ARM64') {
|
||||||
|
$securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/arm64/securityhealthsetup.exe'
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
WriteLog "Windows Security Platform Update URL is $securityPlatformURL"
|
||||||
|
Start-BitsTransferWithRetry -Source $securityPlatformURL -Destination "$DefenderPath\securityhealthsetup.exe"
|
||||||
|
WriteLog "Windows Security Platform Update downloaded to $DefenderPath\securityhealthsetup.exe"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Downloading Windows Security Platform Update Failed"
|
||||||
|
WriteLog "Downloading Windows Security Platform Update Failed with error $_"
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
|
# Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update
|
||||||
|
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update"
|
||||||
|
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
|
$UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Security Platform Update)', ("REM Install Windows Security Platform Update`r`nd:\Defender\securityhealthsetup.exe")
|
||||||
|
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||||
|
WriteLog "Update complete"
|
||||||
}
|
}
|
||||||
if ($UpdateLatestMSRT) {
|
if ($UpdateLatestMSRT) {
|
||||||
WriteLog "`$UpdateLatestMSRT is set to true."
|
WriteLog "`$UpdateLatestMSRT is set to true."
|
||||||
@@ -4371,19 +4564,6 @@ try {
|
|||||||
$KBFilePath = Save-KB -Name $Name -Path $KBPath
|
$KBFilePath = Save-KB -Name $Name -Path $KBPath
|
||||||
WriteLog "Latest .NET saved to $KBPath\$KBFilePath"
|
WriteLog "Latest .NET saved to $KBPath\$KBFilePath"
|
||||||
}
|
}
|
||||||
# #Update Latest Security Platform Update
|
|
||||||
# if ($UpdateSecurityPlatform) {
|
|
||||||
# WriteLog "`$UpdateSecurityPlatform is set to true, checking for latest Security Platform Update"
|
|
||||||
# $Name = "Windows Security platform definition updates"
|
|
||||||
# #Check if $KBPath exists, if not, create it
|
|
||||||
# If (-not (Test-Path -Path $KBPath)) {
|
|
||||||
# WriteLog "Creating $KBPath"
|
|
||||||
# New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
|
||||||
# }
|
|
||||||
# WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $KBPath"
|
|
||||||
# $KBFilePath = Save-KB -Name $Name -Path $KBPath
|
|
||||||
# WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath"
|
|
||||||
# }
|
|
||||||
|
|
||||||
#Search for cached VHDX and skip VHDX creation if there's a cached version
|
#Search for cached VHDX and skip VHDX creation if there's a cached version
|
||||||
if ($AllowVHDXCaching) {
|
if ($AllowVHDXCaching) {
|
||||||
@@ -4392,7 +4572,12 @@ try {
|
|||||||
WriteLog "Found $VHDXCacheFolder"
|
WriteLog "Found $VHDXCacheFolder"
|
||||||
$vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending)
|
$vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending)
|
||||||
WriteLog "Found $($vhdxJsons.Count) cached VHDX files"
|
WriteLog "Found $($vhdxJsons.Count) cached VHDX files"
|
||||||
|
if (Test-Path -Path $KBPath){
|
||||||
$downloadedKBs = @(Get-ChildItem -File -Path $KBPath)
|
$downloadedKBs = @(Get-ChildItem -File -Path $KBPath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$downloadedKBs = @()
|
||||||
|
}
|
||||||
#$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
|
#$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
|
||||||
|
|
||||||
foreach ($vhdxJson in $vhdxJsons) {
|
foreach ($vhdxJson in $vhdxJsons) {
|
||||||
@@ -4407,6 +4592,12 @@ try {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((($vhdxCacheItem.LogicalSectorSizeBytes -ne $LogicalSectorSizeBytes) -or
|
||||||
|
([string]::IsNullOrEmpty($vhdxCacheItem.LogicalSectorSizeBytes) -xor [string]::IsNullOrEmpty($LogicalSectorSizeBytes)))) {
|
||||||
|
WriteLog 'LogicalSectorSizeBytes mismatch, continuing'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or
|
if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or
|
||||||
([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) {
|
([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) {
|
||||||
WriteLog 'WindowsRelease mismatch, continuing'
|
WriteLog 'WindowsRelease mismatch, continuing'
|
||||||
@@ -4528,6 +4719,17 @@ try {
|
|||||||
$Source = Join-Path (Split-Path $wimpath) 'sxs'
|
$Source = Join-Path (Split-Path $wimpath) 'sxs'
|
||||||
Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source
|
Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source
|
||||||
}
|
}
|
||||||
|
If ($ISOPath) {
|
||||||
|
WriteLog 'Dismounting Windows ISO'
|
||||||
|
Dismount-DiskImage -ImagePath $ISOPath | Out-null
|
||||||
|
WriteLog 'Done'
|
||||||
|
}
|
||||||
|
#If $wimPath is an esd file, remove it
|
||||||
|
If ($wimPath -match '.esd') {
|
||||||
|
WriteLog "Deleting $wimPath file"
|
||||||
|
Remove-Item -Path $wimPath -Force
|
||||||
|
WriteLog "$wimPath deleted"
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
#Use cached vhdx file
|
#Use cached vhdx file
|
||||||
@@ -4549,17 +4751,6 @@ try {
|
|||||||
WriteLog "Setting Windows Product Key"
|
WriteLog "Setting Windows Product Key"
|
||||||
Set-WindowsProductKey -Path $WindowsPartition -ProductKey $ProductKey
|
Set-WindowsProductKey -Path $WindowsPartition -ProductKey $ProductKey
|
||||||
}
|
}
|
||||||
If ($ISOPath) {
|
|
||||||
WriteLog 'Dismounting Windows ISO'
|
|
||||||
Dismount-DiskImage -ImagePath $ISOPath | Out-null
|
|
||||||
WriteLog 'Done'
|
|
||||||
}
|
|
||||||
#If $wimPath is an esd file, remove it
|
|
||||||
If ($wimPath -match '.esd') {
|
|
||||||
WriteLog "Deleting $wimPath file"
|
|
||||||
Remove-Item -Path $wimPath -Force
|
|
||||||
WriteLog "$wimPath deleted"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
If ($InstallApps) {
|
If ($InstallApps) {
|
||||||
@@ -4576,7 +4767,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($AllowVHDXCaching -and !$cachedVHDXFileFound) {
|
if ($AllowVHDXCaching -and !$cachedVHDXFileFound) {
|
||||||
WriteLog 'New cached VHDX created'
|
WriteLog 'Caching VHDX file'
|
||||||
|
|
||||||
WriteLog 'Defragmenting Windows partition...'
|
WriteLog 'Defragmenting Windows partition...'
|
||||||
Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority
|
Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority
|
||||||
@@ -4595,16 +4786,26 @@ try {
|
|||||||
$cachedVHDXInfo = [VhdxCacheItem]::new()
|
$cachedVHDXInfo = [VhdxCacheItem]::new()
|
||||||
}
|
}
|
||||||
$cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx")
|
$cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx")
|
||||||
|
$cachedVHDXInfo.LogicalSectorSizeBytes = $LogicalSectorSizeBytes
|
||||||
$cachedVHDXInfo.WindowsSKU = $WindowsSKU
|
$cachedVHDXInfo.WindowsSKU = $WindowsSKU
|
||||||
$cachedVHDXInfo.WindowsRelease = $WindowsRelease
|
$cachedVHDXInfo.WindowsRelease = $WindowsRelease
|
||||||
$cachedVHDXInfo.WindowsVersion = $WindowsVersion
|
$cachedVHDXInfo.WindowsVersion = $WindowsVersion
|
||||||
$cachedVHDXInfo.OptionalFeatures = $OptionalFeatures
|
$cachedVHDXInfo.OptionalFeatures = $OptionalFeatures
|
||||||
|
|
||||||
$cachedVHDXInfo | ConvertTo-Json | Out-File -FilePath ("{0}\{1}_config.json" -f $($VHDXCacheFolder), $VMName)
|
$cachedVHDXInfo | ConvertTo-Json | Out-File -FilePath ("{0}\{1}_config.json" -f $($VHDXCacheFolder), $VMName)
|
||||||
} else {
|
WriteLog "Cached VHDX file $("$VMName.vhdx")"
|
||||||
|
|
||||||
|
#Remount the VHDX file if $installapps is false so the VHDX can be captured to an FFU
|
||||||
|
If (-not $InstallApps) {
|
||||||
|
Mount-Vhd -Path $VHDXPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if($InstallApps){
|
||||||
Dismount-ScratchVhdx -VhdxPath $VHDXPath
|
Dismount-ScratchVhdx -VhdxPath $VHDXPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Host 'Creating VHDX Failed'
|
Write-Host 'Creating VHDX Failed'
|
||||||
WriteLog "Creating VHDX Failed with error $_"
|
WriteLog "Creating VHDX Failed with error $_"
|
||||||
@@ -4857,13 +5058,14 @@ If ($CleanupAppsISO) {
|
|||||||
}
|
}
|
||||||
If ($CleanupDrivers) {
|
If ($CleanupDrivers) {
|
||||||
try {
|
try {
|
||||||
If (Test-Path -Path $Driversfolder\$Make) {
|
#Remove files in $Driversfolder, but keep $DriversFolder
|
||||||
WriteLog "Removing $Driversfolder\$Make"
|
If (Test-Path -Path $Driversfolder) {
|
||||||
Remove-Item -Path $Driversfolder\$Make -Force -Recurse
|
WriteLog "Removing files in $Driversfolder"
|
||||||
WriteLog 'Removal complete'
|
Remove-Item -Path $Driversfolder\* -Force -Recurse
|
||||||
|
WriteLog "Removal complete"
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Writelog "Removing $Driversfolder\$Make failed with error $_"
|
Writelog "Removing $Driversfolder\* failed with error $_"
|
||||||
throw $_
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4879,6 +5081,14 @@ if ($AllowVHDXCaching) {
|
|||||||
throw $_
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#Set $LongPathsEnabled registry value back to original value. $LongPathsEnabled could be $null if the registry value was not found
|
||||||
|
if ($null -eq $LongPathsEnabled) {
|
||||||
|
Remove-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value $LongPathsEnabled
|
||||||
|
}
|
||||||
|
|
||||||
#Clean up dirty.txt file
|
#Clean up dirty.txt file
|
||||||
Remove-Item -Path .\dirty.txt -Force | out-null
|
Remove-Item -Path .\dirty.txt -Force | out-null
|
||||||
if ($VerbosePreference -ne 'Continue'){
|
if ($VerbosePreference -ne 'Continue'){
|
||||||
|
|||||||
@@ -55,11 +55,18 @@ function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){
|
|||||||
|
|
||||||
function Set-Computername($computername){
|
function Set-Computername($computername){
|
||||||
[xml]$xml = Get-Content $UnattendFile
|
[xml]$xml = Get-Content $UnattendFile
|
||||||
if($xml.unattend.settings.component.Count -ge 2){
|
$components = $xml.unattend.settings.component
|
||||||
#Assumes that Computername is the first component element
|
$found = $false
|
||||||
$xml.unattend.settings.component[0].ComputerName = $computername
|
foreach ($component in $components) {
|
||||||
}else{
|
if ($component.ComputerName) {
|
||||||
$xml.unattend.settings.component.ComputerName = $computername
|
$component.ComputerName = $computername
|
||||||
|
$found = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $found) {
|
||||||
|
WriteLog 'ComputerName element not found in unattend.xml.'
|
||||||
|
throw 'ComputerName element not found in unattend.xml.'
|
||||||
}
|
}
|
||||||
$xml.Save($UnattendFile)
|
$xml.Save($UnattendFile)
|
||||||
return $computername
|
return $computername
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user