Compare commits

...

23 Commits

Author SHA1 Message Date
rbalsleyMSFT 60d147c71d Update README.md 2024-09-10 11:45:44 -07:00
rbalsleyMSFT cd36150ddc Merge pull request #71 from rbalsleyMSFT/2409.1
2409.1
2024-09-10 11:44:49 -07:00
rbalsleyMSFT 198a544dbb update change log 2024-09-10 11:44:12 -07:00
rbalsleyMSFT 40616776eb - Added VMHostIPAddress and VMSwitchName validation to validate the IP address matches the VMSwitchName 2024-09-07 09:53:14 -07:00
rbalsleyMSFT 20c9cf8ab3 Merge pull request #65 from HedgeComp/Time-to-Complete-Hours
Time To Complete Shows Hours if Needed
2024-09-06 11:26:54 -07:00
rbalsleyMSFT 17558f86aa Merge pull request #64 from HedgeComp/OneDrive-Silent
Silently Install Onedrive
2024-09-06 11:26:15 -07:00
rbalsleyMSFT 7408dbb435 Merge branch '2409.1' of https://github.com/rbalsleyMSFT/FFU into 2409.1 2024-09-06 11:24:21 -07:00
rbalsleyMSFT 9c1fc59af9 - Added new variables for the PPKG, Unattend, Autopilot, and PEDrivers validation 2024-09-06 11:24:16 -07:00
HedgeComp 31c785b5da Update BuildFFUVM.ps1
Add Hours to Total Time to complete only if greater than 1 hour
2024-09-06 13:00:09 -04:00
HedgeComp 5b93135ebb Update BuildFFUVM.ps1
Add Silent switch install to Onedrivesetup.exe
2024-09-06 12:57:36 -04:00
rbalsleyMSFT 5f4cf0c66e Merge pull request #60 from zehadialam/2409.1
Fix for Missing Windows Boot Manager Entry
2024-09-06 09:34:11 -07:00
Zehadi Alam ddbf2b0339 Use bcdedit to set Windows Boot Manager and default Windows boot loader to be first in the display order of UEFI firmware 2024-09-05 17:57:35 -04:00
rbalsleyMSFT e62d481405 - Remove ValidateScript on InstallDrivers and break it out in a validation block so -Make and -Model can be specified anywhere in the command line
- Check for Prefixes.txt file and copy to the USB drive if it exists
- Perform better validation for PPKG, Unattend, Autopilot json, and drivers
- Comment out the Windows Security Platform update as the file has been removed from the MU Catalog.
2024-09-04 17:05:06 -07:00
rbalsleyMSFT 6c07ac8595 Merge branch '2409.1' of https://github.com/rbalsleyMSFT/FFU into 2409.1 2024-09-04 13:20:38 -07:00
rbalsleyMSFT 7d74feec0c - Fix an issue with removal of Defender/OneDrive/Edge after FFU is complete
- Migrate Winget downloads to use Export-WingetPackage cmdlet as per issue Known Issue: Winget downloads fail on Non-English OS #50
- Add better logging when unable to find HDD when applying FFU
2024-09-04 13:20:35 -07:00
rbalsleyMSFT 6b2a4bcb27 Merge pull request #51 from HedgeComp/2408.2
Allow Preview CU update
2024-09-04 13:08:58 -07:00
rbalsleyMSFT 6da9ece0d8 Merge pull request #54 from w0/main
use ValidateSet for WindowsSKU
2024-08-15 18:59:01 -07:00
w0 dc4438dcf9 use ValidateSet for WindowsSKU 2024-08-15 20:11:41 -05:00
rbalsleyMSFT e250e2a130 Changed some logging when winget apps can't be found 2024-08-13 16:11:36 -07:00
Doctair dad51fdf80 updated Docs to relect new $UpdatePreviewCU Parameter 2024-08-12 13:15:16 -04:00
Doctair d60b0301c5 Remove a temp copy of BUildScript 2024-08-12 10:15:45 -04:00
Doctair db3e09650a Add new Parameter for Installing Preview CU from
Mircosoft Update Catalog. Recent Windows Pro
not Auto Activating to Enterprise License Bug speared
this idea as its resoleve in lastes Prieview CU.

Parameter is default $False but if set to $true will install
Preivew CU and take precendence over $UpdateLastestCU.
2024-08-12 10:07:41 -04:00
rbalsleyMSFT bcb9911cd0 Small update to fix a logging issue with script run time duration 2024-08-07 13:01:26 -07:00
6 changed files with 517 additions and 221 deletions
+16
View File
@@ -1,5 +1,21 @@
# Change Log # Change Log
## **2409.1**
### Fixes
- Fix an issue with removal of Defender/OneDrive/Edge after FFU is complete
- Migrate Winget downloads to use [Export-WingetPackage cmdlet](https://github.com/microsoft/winget-cli/blob/master/doc/specs/%23658%20-%20WinGet%20Download.md#winget-powershell-cmdlet) as per issue #50
- Add support for preview updates https://github.com/rbalsleyMSFT/FFU/pull/51 - thanks to @HedgeComp
- Refactor validation of Unattend/prefixes, PPKG, Autopilot to check for these files early in the process, similar to how we check for drivers
- Add better logging when unable to find HDD when applying FFU. Will inform to add WinPE drivers to Deployment Media if HDD not found.
- Remove ValidateScript on InstallDrivers and break it out in a validation block so -Make and -Model can be specified anywhere in the command line
- Add validation for VMHostIPAddress and VMSwtichName and inform the user if these don't match. Should prevent issues where the FFU isn't getting created.
- Removed installation of the Windows Security Platform Update as it has been removed from the MU Catalog. See issue #58
- Thanks to w0 for PR #54 to change the validation set for WindowsSKU
- Thanks to @zehadialam for PR #60 to fix an issue with Windows boot loader for certain devices where Windows Boot Manager is not the first boot entry after the FFU is applied.
- Thanks to @HedgeComp for PR #64 and PR #65
## **2408.1** ## **2408.1**
### External Drive Support ### External Drive Support
+482 -213
View File
@@ -106,6 +106,9 @@ When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU fol
.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.
.PARAMETER UpdatePreviewCU
When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false.
.PARAMETER UpdateLatestNet .PARAMETER UpdateLatestNet
When set to $true, will download and install the latest .NET Framework 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.
@@ -198,11 +201,7 @@ param(
[Parameter(Mandatory = $false, Position = 0)] [Parameter(Mandatory = $false, Position = 0)]
[ValidateScript({ Test-Path $_ })] [ValidateScript({ Test-Path $_ })]
[string]$ISOPath, [string]$ISOPath,
[ValidateScript({ [ValidateSet('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')]
$allowedSKUs = @('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')
if ($allowedSKUs -contains $_) { $true } else { throw "Invalid WindowsSKU value. Allowed values: $($allowedSKUs -join ', ')" }
return $true
})]
[string]$WindowsSKU = 'Pro', [string]$WindowsSKU = 'Pro',
[ValidateScript({ Test-Path $_ })] [ValidateScript({ Test-Path $_ })]
[string]$FFUDevelopmentPath = $PSScriptRoot, [string]$FFUDevelopmentPath = $PSScriptRoot,
@@ -211,16 +210,16 @@ param(
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')] [ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
[string]$Make, [string]$Make,
[string]$Model, [string]$Model,
[Parameter(Mandatory = $false)] # [Parameter(Mandatory = $false)]
[ValidateScript({ # [ValidateScript({
if ($Make) { # if ($Make) {
return $true # return $true
} # }
if ($_ -and (!(Test-Path -Path '.\Drivers') -or ((Get-ChildItem -Path '.\Drivers' -Recurse | Measure-Object -Property Length -Sum).Sum -lt 1MB))) { # 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' # throw 'InstallDrivers is set to $true, but either the Drivers folder is missing or empty'
} # }
return $true # return $true
})] # })]
[bool]$InstallDrivers, [bool]$InstallDrivers,
[uint64]$Memory = 4GB, [uint64]$Memory = 4GB,
[uint64]$Disksize = 30GB, [uint64]$Disksize = 30GB,
@@ -304,6 +303,7 @@ param(
[bool]$CopyPEDrivers, [bool]$CopyPEDrivers,
[bool]$RemoveFFU, [bool]$RemoveFFU,
[bool]$UpdateLatestCU, [bool]$UpdateLatestCU,
[bool]$UpdatePreviewCU,
[bool]$UpdateLatestNet, [bool]$UpdateLatestNet,
[bool]$UpdateLatestDefender, [bool]$UpdateLatestDefender,
[bool]$UpdateEdge, [bool]$UpdateEdge,
@@ -336,7 +336,7 @@ param(
[bool]$AllowExternalHardDiskMedia, [bool]$AllowExternalHardDiskMedia,
[bool]$PromptExternalHardDiskMedia = $true [bool]$PromptExternalHardDiskMedia = $true
) )
$version = '2408.1' $version = '2409.1'
#Check if Hyper-V feature is installed (requires only checks the module) #Check if Hyper-V feature is installed (requires only checks the module)
$osInfo = Get-WmiObject -Class Win32_OperatingSystem $osInfo = Get-WmiObject -Class Win32_OperatingSystem
@@ -375,6 +375,11 @@ if (-not $DefenderPath) { $DefenderPath = "$AppsPath\Defender" }
if (-not $OneDrivePath) { $OneDrivePath = "$AppsPath\OneDrive" } if (-not $OneDrivePath) { $OneDrivePath = "$AppsPath\OneDrive" }
if (-not $EdgePath) { $EdgePath = "$AppsPath\Edge" } if (-not $EdgePath) { $EdgePath = "$AppsPath\Edge" }
if (-not $DriversFolder) { $DriversFolder = "$FFUDevelopmentPath\Drivers" } if (-not $DriversFolder) { $DriversFolder = "$FFUDevelopmentPath\Drivers" }
if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" }
if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" }
if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" }
if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" }
#FUNCTIONS #FUNCTIONS
function WriteLog($LogText) { function WriteLog($LogText) {
@@ -1683,33 +1688,82 @@ function Install-WinGet {
WriteLog "Downloading $($package.Name) from $($package.Url) to $destination" WriteLog "Downloading $($package.Name) from $($package.Url) to $destination"
Start-BitsTransferWithRetry -Source $package.Url -Destination $destination Start-BitsTransferWithRetry -Source $package.Url -Destination $destination
WriteLog "Installing $($package.Name)..." WriteLog "Installing $($package.Name)..."
# Don't show progress bar for Add-AppxPackage - there's a weird issue where the progress stays on the screen after the apps are installed
$ProgressPreference = 'SilentlyContinue'
Add-AppxPackage -Path $destination -ErrorAction SilentlyContinue Add-AppxPackage -Path $destination -ErrorAction SilentlyContinue
# Set progress preference back to default
$ProgressPreference = 'Continue'
WriteLog "Removing $($package.Name)..." WriteLog "Removing $($package.Name)..."
Remove-Item -Path $destination -Force -ErrorAction SilentlyContinue Remove-Item -Path $destination -Force -ErrorAction SilentlyContinue
} }
WriteLog "WinGet installation complete." WriteLog "WinGet installation complete."
} }
# function Confirm-WinGetInstallation {
# WriteLog 'Checking if WinGet is installed...'
# $wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe"
# $minVersion = [version]"1.8.1911"
# if (-not (Test-Path -Path $wingetPath -PathType Leaf)) {
# WriteLog "WinGet is not installed. Downloading WinGet..."
# Install-WinGet -Architecture $WindowsArch
# }
# if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) {
# WriteLog "WinGet not found. Downloading WinGet..."
# Install-WinGet -Architecture $WindowsArch
# }
# $wingetVersion = & winget.exe --version
# WriteLog "Installed version of WinGet: $wingetVersion"
# if ($wingetVersion -match 'v?(\d+\.\d+\.\d+)' -and [version]$matches[1] -lt $minVersion) {
# WriteLog "The installed version of WinGet $($matches[1]) does not support downloading MSStore apps. Downloading the latest version of WinGet..."
# Install-WinGet -Architecture $WindowsArch
# }
# # Check if Winget PowerShell module version 1.8.1911 or later is installed
# $wingetModule = Get-InstalledModule -Name Microsoft.Winget.Client -ErrorAction SilentlyContinue
# if ($wingetModule.Version -lt $minVersion -or -not $wingetModule) {
# WriteLog 'Microsoft.Winget.Client module is not installed or is an older version. Installing the latest version...'
# #Check if PSGallery is a trusted repository
# $PSGalleryTrust = (Get-PSRepository -Name 'PSGallery').InstallationPolicy
# if($PSGalleryTrust -eq 'Untrusted'){
# WriteLog 'Temporarily setting PSGallery as a trusted repository...'
# Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
# }
# Install-Module -Name Microsoft.Winget.Client -Force -Repository 'PSGallery'
# if($PSGalleryTrust -eq 'Untrusted'){
# WriteLog 'Setting PSGallery back to untrusted repository...'
# Set-PSRepository -Name 'PSGallery' -InstallationPolicy Untrusted
# WriteLog 'Done'
# }
# }
# }
function Confirm-WinGetInstallation { function Confirm-WinGetInstallation {
WriteLog 'Checking if WinGet is installed...' WriteLog 'Checking if WinGet is installed...'
$wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe"
$minVersion = [version]"1.8.1911" $minVersion = [version]"1.8.1911"
if (-not (Test-Path -Path $wingetPath -PathType Leaf)) { # Check if Winget PowerShell module version 1.8.1911 or later is installed
WriteLog "WinGet is not installed. Downloading WinGet..." $wingetModule = Get-InstalledModule -Name Microsoft.Winget.Client -ErrorAction SilentlyContinue
Install-WinGet -Architecture $WindowsArch if ($wingetModule.Version -lt $minVersion -or -not $wingetModule) {
return WriteLog 'Microsoft.Winget.Client module is not installed or is an older version. Installing the latest version...'
#Check if PSGallery is a trusted repository
$PSGalleryTrust = (Get-PSRepository -Name 'PSGallery').InstallationPolicy
if($PSGalleryTrust -eq 'Untrusted'){
WriteLog 'Temporarily setting PSGallery as a trusted repository...'
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
} }
if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) { Install-Module -Name Microsoft.Winget.Client -Force -Repository 'PSGallery'
WriteLog "WinGet not found. Downloading WinGet..." if($PSGalleryTrust -eq 'Untrusted'){
Install-WinGet -Architecture $WindowsArch WriteLog 'Setting PSGallery back to untrusted repository...'
return Set-PSRepository -Name 'PSGallery' -InstallationPolicy Untrusted
WriteLog 'Done'
} }
$wingetVersion = & winget.exe --version }
WriteLog "Installed version of WinGet: $wingetVersion" $wingetVersion = Get-WinGetVersion
if ($wingetVersion -match 'v?(\d+\.\d+\.\d+)' -and [version]$matches[1] -lt $minVersion) { if (-not $wingetVersion) {
WriteLog "The installed version of WinGet $($matches[1]) does not support downloading MSStore apps. Downloading the latest version of WinGet..." WriteLog "WinGet is not installed. Installing WinGet..."
Install-WinGet -Architecture $WindowsArch
}
if (($wingetVersion -match 'v?(\d+\.\d+\.\d+)' -and [version]$matches[1] -lt $minVersion)) {
WriteLog "The installed version of WinGet $($matches[1]) does not support downloading MSStore apps. Installing the latest version of WinGet..."
Install-WinGet -Architecture $WindowsArch Install-WinGet -Architecture $WindowsArch
return
} }
} }
@@ -1757,43 +1811,106 @@ function Set-InstallStoreAppsFlag {
} }
} }
# function Get-WinGetApp {
# param (
# [string]$WinGetAppName,
# [string]$WinGetAppId
# )
# $wingetSearchResult = & winget.exe search --id "$WinGetAppId" --exact --accept-source-agreements --source winget
# if ($wingetSearchResult -contains "No package found matching input criteria.") {
# if ($VerbosePreference -ne 'Continue'){
# Write-Error "$WinGetAppName not found in WinGet repository. Skipping download."
# Write-Error "Check the AppList.json file and make sure the AppID is correct."
# Write-Error "If OS language is not English, winget download may fail. We hope to have this addressed in a future release."
# }
# WriteLog "$WinGetAppName not found in WinGet repository. Exiting."
# WriteLog "Check the AppList.json file and make sure the AppID is correct."
# WriteLog "If OS language is not English, winget download may fail. We hope to have this addressed in a future release."
# Exit 1
# }
# $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName
# WriteLog "Creating $appFolderPath"
# New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
# WriteLog "Downloading $WinGetAppName to $appFolderPath"
# $downloadParams = @(
# "download",
# "--id", "$WinGetAppId",
# "--exact",
# "--download-directory", "$appFolderPath",
# "--accept-package-agreements",
# "--accept-source-agreements",
# "--source", "winget",
# "--scope", "machine",
# "--architecture", "$WindowsArch"
# )
# WriteLog "winget command: winget.exe $downloadParams"
# $wingetDownloadResult = & winget.exe @downloadParams | Out-String
# if ($wingetDownloadResult -match "No applicable installer found") {
# WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
# $downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" }
# $wingetDownloadResult = & winget.exe @downloadParams | Out-String
# if ($wingetDownloadResult -match "Installer downloaded") {
# WriteLog "Downloaded $WinGetAppName without specifying architecture."
# }
# }
# if ($wingetDownloadResult -notmatch "Installer downloaded") {
# WriteLog "No installer found for $WinGetAppName. Skipping download."
# Remove-Item -Path $appFolderPath -Recurse -Force
# }
# WriteLog "$WinGetAppName downloaded to $appFolderPath"
# $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop
# $uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle")
# if ($uwpExtensions -contains $installerPath.Extension) {
# $NewAppPath = "$AppsPath\MSStore\$WinGetAppName"
# Writelog "$WinGetAppName is a UWP app. Moving to $NewAppPath"
# WriteLog "Creating $NewAppPath"
# New-Item -Path "$AppsPath\MSStore\$WinGetAppName" -ItemType Directory -Force | Out-Null
# WriteLog "Moving $WinGetAppName to $NewAppPath"
# Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$WinGetAppName" -Force
# WriteLog "Removing $appFolderPath"
# Remove-Item -Path $appFolderPath -Force
# WriteLog "$WinGetAppName moved to $NewAppPath"
# Set-InstallStoreAppsFlag
# }
# else {
# Add-Win32SilentInstallCommand -AppFolder $WinGetAppName -AppFolderPath $appFolderPath
# }
# }
function Get-WinGetApp { function Get-WinGetApp {
param ( param (
[string]$WinGetAppName, [string]$WinGetAppName,
[string]$WinGetAppId [string]$WinGetAppId
) )
$wingetSearchResult = & winget.exe search --id "$WinGetAppId" --exact --accept-source-agreements --source winget $Source = 'winget'
if ($wingetSearchResult -contains "No package found matching input criteria.") { $wingetSearchResult = Find-WinGetPackage -id $WinGetAppId -MatchOption Equals -Source $Source
WriteLog "$WinGetAppName not found in WinGet repository. Skipping download." if (-not $wingetSearchResult) {
if ($VerbosePreference -ne 'Continue'){
Write-Error "$WinGetAppName not found in WinGet repository. Exiting."
Write-Error "Check the AppList.json file and make sure the AppID is correct."
}
WriteLog "$WinGetAppName not found in WinGet repository. Exiting."
WriteLog "Check the AppList.json file and make sure the AppID is correct."
Exit 1
} }
$appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName
WriteLog "Creating $appFolderPath" WriteLog "Creating $appFolderPath"
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
WriteLog "Downloading $WinGetAppName to $appFolderPath" WriteLog "Downloading $WinGetAppName to $appFolderPath"
$downloadParams = @(
"download", WriteLog "WinGet command: Export-WinGetPackage -id $WinGetAppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source"
"--id", "$WinGetAppId", $wingetDownloadResult = Export-WinGetPackage -id $WinGetAppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source
"--exact", if ($wingetDownloadResult.status -eq 'NoApplicableInstallers') {
"--download-directory", "$appFolderPath", # If no applicable installer is found, try downloading without specifying architecture
"--accept-package-agreements",
"--accept-source-agreements",
"--source", "winget",
"--scope", "machine",
"--architecture", "$WindowsArch"
)
WriteLog "winget command: winget.exe $downloadParams"
$wingetDownloadResult = & winget.exe @downloadParams | Out-String
if ($wingetDownloadResult -match "No applicable installer found") {
WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
$downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" } $wingetDownloadResult = Export-WinGetPackage -id $WinGetAppId -DownloadDirectory $appFolderPath -Source $Source
$wingetDownloadResult = & winget.exe @downloadParams | Out-String if ($wingetDownloadResult.status -eq 'Ok') {
if ($wingetDownloadResult -match "Installer downloaded") {
WriteLog "Downloaded $WinGetAppName without specifying architecture." WriteLog "Downloaded $WinGetAppName without specifying architecture."
} }
} else{
if ($wingetDownloadResult -notmatch "Installer downloaded") { WriteLog "No installer found for $WinGetAppName. Exiting."
WriteLog "No installer found for $WinGetAppName. Skipping download."
Remove-Item -Path $appFolderPath -Recurse -Force Remove-Item -Path $appFolderPath -Recurse -Force
Exit 1
}
} }
WriteLog "$WinGetAppName downloaded to $appFolderPath" WriteLog "$WinGetAppName downloaded to $appFolderPath"
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop
@@ -1815,15 +1932,105 @@ function Get-WinGetApp {
} }
} }
# function Get-StoreApp {
# param (
# [string]$StoreAppName,
# [string]$StoreAppId
# )
# $wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore
# if ($wingetSearchResult -contains "No package found matching input criteria.") {
# WriteLog "$StoreAppName not found in WinGet repository. Skipping download."
# return
# }
# WriteLog "Checking if $StoreAppName is a win32 app..."
# $appIsWin32 = $StoreAppId.StartsWith("XP")
# if ($appIsWin32) {
# WriteLog "$StoreAppName is a win32 app. Adding to $AppsPath\win32 folder"
# $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreAppName
# }
# else {
# WriteLog "$StoreAppName is not a win32 app."
# $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreAppName
# }
# New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
# WriteLog "Downloading $StoreAppName for $WindowsArch architecture..."
# $downloadParams = @(
# "download", "$StoreAppId",
# "--download-directory", "$appFolderPath",
# "--accept-package-agreements",
# "--accept-source-agreements",
# "--source", "msstore",
# "--scope", "machine",
# "--architecture", "$WindowsArch"
# )
# WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.'
# WriteLog "Attempting to download $StoreAppName and dependencies for $WindowsArch architecture..."
# $wingetDownloadResult = & winget.exe @downloadParams | Out-String
# # For some apps, specifying the architecture leads to no results found for the app. In those cases, the command will be run without the architecture parameter.
# if ($wingetDownloadResult -match "No applicable installer found") {
# WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
# $downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" }
# $wingetDownloadResult = & winget.exe @downloadParams | Out-String
# if ($wingetDownloadResult -match "Microsoft Store package download completed") {
# WriteLog "Downloaded $StoreAppName without specifying architecture."
# }
# }
# if ($wingetDownloadResult -notmatch "Installer downloaded|Microsoft Store package download completed") {
# WriteLog "Download not supported for $StoreAppName. Skipping download."
# Remove-Item -Path $appFolderPath -Recurse -Force
# return
# }
# if ($appIsWin32) {
# Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath
# }
# Set-InstallStoreAppsFlag
# # If $WindowsArch -eq 'ARM64', remove all dependency files that are not ARM64
# if ($WindowsArch -eq 'ARM64') {
# WriteLog 'Windows architecture is ARM64. Removing dependencies that are not ARM64.'
# $dependencies = Get-ChildItem -Path "$appFolderPath\Dependencies" -ErrorAction SilentlyContinue
# if ($dependencies) {
# foreach ($dependency in $dependencies) {
# if ($dependency.Name -notmatch 'ARM64') {
# WriteLog "Removing dependency file $($dependency.FullName)"
# Remove-Item -Path $dependency.FullName -Recurse -Force
# }
# }
# }
# }
# WriteLog "$StoreAppName has completed downloading. Identifying the latest version of $StoreAppName."
# $packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File -ErrorAction Stop
# # WinGet downloads multiple versions of certain store apps. The latest version of the package will be determined based on the date of the file signature.
# $latestPackage = $packages | Sort-Object { (Get-AuthenticodeSignature $_.FullName).SignerCertificate.NotBefore } -Descending | Select-Object -First 1
# # Removing all packages that are not the latest version
# WriteLog "Latest version of $StoreAppName has been identified as $latestPackage. Removing old versions of $StoreAppName that may have downloaded."
# foreach ($package in $packages) {
# if ($package.FullName -ne $latestPackage) {
# try {
# WriteLog "Removing $($package.FullName)"
# Remove-Item -Path $package.FullName -Force
# }
# catch {
# WriteLog "Failed to delete: $($package.FullName) - $_"
# throw $_
# }
# }
# }
# }
function Get-StoreApp { function Get-StoreApp {
param ( param (
[string]$StoreAppName, [string]$StoreAppName,
[string]$StoreAppId [string]$StoreAppId
) )
$wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore $Source = 'msstore'
if ($wingetSearchResult -contains "No package found matching input criteria.") { $wingetSearchResult = Find-WinGetPackage -id $StoreAppId -MatchOption Equals -Source $Source
WriteLog "$StoreAppName not found in WinGet repository. Skipping download." if (-not $wingetSearchResult) {
return if ($VerbosePreference -ne 'Continue'){
Write-Error "$WinGetAppName not found in WinGet repository. Exiting."
Write-Error "Check the AppList.json file and make sure the AppID is correct."
}
WriteLog "$WinGetAppName not found in WinGet repository. Exiting."
WriteLog "Check the AppList.json file and make sure the AppID is correct."
Exit 1
} }
WriteLog "Checking if $StoreAppName is a win32 app..." WriteLog "Checking if $StoreAppName is a win32 app..."
$appIsWin32 = $StoreAppId.StartsWith("XP") $appIsWin32 = $StoreAppId.StartsWith("XP")
@@ -1837,31 +2044,22 @@ function Get-StoreApp {
} }
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
WriteLog "Downloading $StoreAppName for $WindowsArch architecture..." WriteLog "Downloading $StoreAppName for $WindowsArch architecture..."
$downloadParams = @(
"download", "$StoreAppId",
"--download-directory", "$appFolderPath",
"--accept-package-agreements",
"--accept-source-agreements",
"--source", "msstore",
"--scope", "machine",
"--architecture", "$WindowsArch"
)
WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.' WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.'
WriteLog "Attempting to download $StoreAppName and dependencies for $WindowsArch architecture..." WriteLog "Attempting to download $StoreAppName and dependencies for $WindowsArch architecture..."
$wingetDownloadResult = & winget.exe @downloadParams | Out-String WriteLog "WinGet command: Export-WinGetPackage -id $StoreAppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source"
# For some apps, specifying the architecture leads to no results found for the app. In those cases, the command will be run without the architecture parameter. $wingetDownloadResult = Export-WinGetPackage -id $StoreAppId -DownloadDirectory $appFolderPath -Architecture $WindowsArch -Source $Source
if ($wingetDownloadResult -match "No applicable installer found") { if ($wingetDownloadResult.status -eq 'NoApplicableInstallerFound') {
# If no applicable installer is found, try downloading without specifying architecture
WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
$downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" } $wingetDownloadResult = Export-WinGetPackage -id $StoreAppId -DownloadDirectory $appFolderPath -Source $Source
$wingetDownloadResult = & winget.exe @downloadParams | Out-String if ($wingetDownloadResult.status -eq 'Ok') {
if ($wingetDownloadResult -match "Microsoft Store package download completed") { WriteLog "Downloaded $WinGetAppName without specifying architecture."
WriteLog "Downloaded $StoreAppName without specifying architecture."
} }
} else{
if ($wingetDownloadResult -notmatch "Installer downloaded|Microsoft Store package download completed") { WriteLog "No installer found for $WinGetAppName. Exiting"
WriteLog "Download not supported for $StoreAppName. Skipping download."
Remove-Item -Path $appFolderPath -Recurse -Force Remove-Item -Path $appFolderPath -Recurse -Force
return Exit 1
}
} }
if ($appIsWin32) { if ($appIsWin32) {
Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath
@@ -2050,68 +2248,6 @@ function Save-KB {
if ($WindowsArch -eq 'x64') { if ($WindowsArch -eq 'x64') {
[array]$WindowsArch = @("x64", "amd64") [array]$WindowsArch = @("x64", "amd64")
} }
#Keep for now, will remove in future
# foreach ($kb in $name) {
# $links = Get-KBLink -Name $kb
# foreach ($link in $links) {
# #Check if $WindowsArch is an array
# if ($WindowsArch -is [array]) {
# #Some file names include either x64 or amd64
# if ($link -match $WindowsArch[0] -or $link -match $WindowsArch[1]) {
# Start-BitsTransferWithRetry -Source $link -Destination $Path
# $fileName = ($link -split '/')[-1]
# break
# }
# # elseif (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
# # Write-Host "No architecture found in $link, assume it's for all architectures"
# # Start-BitsTransfer -Source $link -Destination $Path
# # $fileName = ($link -split '/')[-1]
# # break
# # }
# elseif (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
# WriteLog "No architecture found in $link, assume this is for all architectures"
# #FIX: 3/22/2024 - the SecurityHealthSetup fix was updated and now includes two files (one is x64 and the other is arm64)
# #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
# }
# }
# elseif ($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
# }
# }
# continue
# }
# Start-BitsTransferWithRetry -Source $link -Destination $Path
# $fileName = ($link -split '/')[-1]
# }
# }
# else {
# if ($link -match $WindowsArch) {
# Start-BitsTransferWithRetry -Source $link -Destination $Path
# $fileName = ($link -split '/')[-1]
# break
# }
# }
# }
# }
foreach ($kb in $name) { foreach ($kb in $name) {
$links = Get-KBLink -Name $kb $links = Get-KBLink -Name $kb
foreach ($link in $links) { foreach ($link in $links) {
@@ -3228,9 +3364,14 @@ Function New-DeploymentUSB {
if ($WindowsArch -eq 'x64') { if ($WindowsArch -eq 'x64') {
Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_x64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_x64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null
} }
else { if ($WindowsArch -eq 'arm64') {
Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_arm64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_arm64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null
} }
#Check for prefixes.txt file and copy it to the USB drive
if (Test-Path "$FFUDevelopmentPath\unattend\prefixes.txt") {
WriteLog "Copying prefixes.txt file to $DeployUnattendPath"
Copy-Item -Path "$FFUDevelopmentPath\unattend\prefixes.txt" -Destination "$DeployUnattendPath\prefixes.txt" -Force | Out-Null
}
WriteLog 'Copy completed' WriteLog 'Copy completed'
} }
#Copy PPKG folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder #Copy PPKG folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder
@@ -3322,9 +3463,14 @@ function Get-FFUEnvironment {
foreach ($image in $mountedImages) { foreach ($image in $mountedImages) {
$mountPath = $image.Path $mountPath = $image.Path
WriteLog "Dismounting image at $mountPath" WriteLog "Dismounting image at $mountPath"
try {
Dismount-WindowsImage -Path $mountPath -discard | Out-null Dismount-WindowsImage -Path $mountPath -discard | Out-null
WriteLog "Successfully dismounted image at $mountPath" WriteLog "Successfully dismounted image at $mountPath"
} }
catch {
WriteLog "Failed to dismount image at $mountPath with error: $_"
}
}
} }
# Remove Mount folder if it exists # Remove Mount folder if it exists
@@ -3352,6 +3498,7 @@ function Get-FFUEnvironment {
Remove-FFUUserShare Remove-FFUUserShare
WriteLog 'Removal complete' WriteLog 'Removal complete'
} }
Clear-InstallAppsandSysprep
#Clean up $KBPath #Clean up $KBPath
If (Test-Path -Path $KBPath) { If (Test-Path -Path $KBPath) {
WriteLog "Removing $KBPath" WriteLog "Removing $KBPath"
@@ -3384,7 +3531,6 @@ function Get-FFUEnvironment {
WriteLog "Cleaning up MSStore folder" WriteLog "Cleaning up MSStore folder"
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force -ErrorAction SilentlyContinue
} }
Clear-InstallAppsandSysprep
Writelog 'Removing dirty.txt file' Writelog 'Removing dirty.txt file'
Remove-Item -Path "$FFUDevelopmentPath\dirty.txt" -Force Remove-Item -Path "$FFUDevelopmentPath\dirty.txt" -Force
WriteLog "Cleanup complete" WriteLog "Cleanup complete"
@@ -3408,29 +3554,34 @@ function Clear-InstallAppsandSysprep {
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Defender Platform Update" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Defender Platform Update"
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
$CmdContent -notmatch 'd:\\Defender*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent -notmatch 'd:\\Defender*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
# #Remove $DefenderPath #Clean up $DefenderPath
# WriteLog "Removing $DefenderPath" If (Test-Path -Path $DefenderPath) {
# Remove-Item -Path $DefenderPath -Recurse -Force WriteLog "Removing $DefenderPath"
# WriteLog "Removal complete" Remove-Item -Path $DefenderPath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
} }
if ($UpdateOneDrive) { if ($UpdateOneDrive) {
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove OneDrive install" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove OneDrive install"
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
$CmdContent -notmatch 'd:\\OneDrive*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent -notmatch 'd:\\OneDrive*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
# #Remove $OneDrivePath #Clean up $OneDrivePath
# WriteLog "Removing $OneDrivePath" If (Test-Path -Path $OneDrivePath) {
# Remove-Item -Path $OneDrivePath -Recurse -Force WriteLog "Removing $OneDrivePath"
# WriteLog "Removal complete" Remove-Item -Path $OneDrivePath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
} }
if ($UpdateEdge) { if ($UpdateEdge) {
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Edge install" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Edge install"
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
$CmdContent -notmatch 'd:\\Edge*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent -notmatch 'd:\\Edge*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
# #Remove $EdgePath #Clean up $EdgePath
# WriteLog "Removing $EdgePath" If (Test-Path -Path $EdgePath) {
# Remove-Item -Path $EdgePath -Recurse -Force WriteLog "Removing $EdgePath"
# WriteLog "Removal complete" Remove-Item -Path $EdgePath -Recurse -Force -ErrorAction SilentlyContinue
WriteLog 'Removal complete'
}
} }
} }
@@ -3447,6 +3598,84 @@ Write-Host "To track progress, please open the log file $Logfile or use the -Ver
WriteLog 'Begin Logging' WriteLog 'Begin Logging'
###PARAMETER VALIDATION
#Validate drivers folder
if ($InstallDrivers -or $CopyDrivers) {
WriteLog 'Doing driver validation'
if ($Make -and $Model){
WriteLog "Make and Model are set to $Make and $Model, will attempt to download drivers"
} else {
if (!(Test-Path -Path $DriversFolder)) {
WriteLog "-InstallDrivers or -CopyDrivers is set to `$true, but the $DriversFolder folder is missing"
throw "-InstallDrivers or -CopyDrivers is set to `$true, but the $DriversFolder folder is missing"
}
if ((Get-ChildItem -Path $DriversFolder -Recurse | Measure-Object -Property Length -Sum).Sum -lt 1MB) {
WriteLog "-InstallDrivers or -CopyDrivers is set to `$true, but the $DriversFolder folder is empty"
throw "-InstallDrivers or -CopyDrivers is set to `$true, but the $DriversFolder folder is empty"
}
WriteLog 'Driver validation complete'
}
}
#Validate PEDrivers folder
if ($CopyPEDrivers) {
WriteLog 'Doing PEDriver validation'
if (!(Test-Path -Path $PEDriversFolder)) {
WriteLog "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
throw "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is missing"
}
if ((Get-ChildItem -Path $PEDriversFolder -Recurse | Measure-Object -Property Length -Sum).Sum -lt 1MB) {
WriteLog "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is empty"
throw "-CopyPEDrivers is set to `$true, but the $PEDriversFolder folder is empty"
}
WriteLog 'PEDriver validation complete'
}
#Validate PPKG folder
if ($CopyPPKG) {
WriteLog 'Doing PPKG validation'
if (!(Test-Path -Path $PPKGFolder)) {
WriteLog "-CopyPPKG is set to `$true, but the $PPKGFolder folder is missing"
throw "-CopyPPKG is set to `$true, but the $PPKGFolder folder is missing"
}
#Check for at least one .PPKG file
if (!(Get-ChildItem -Path $PPKGFolder -Filter *.ppkg)) {
WriteLog "-CopyPPKG is set to `$true, but the $PPKGFolder folder is missing a .PPKG file"
throw "-CopyPPKG is set to `$true, but the $PPKGFolder folder is missing a .PPKG file"
}
WriteLog 'PPKG validation complete'
}
#Validate Autopilot folder
if ($CopyAutopilot) {
WriteLog 'Doing Autopilot validation'
if (!(Test-Path -Path $AutopilotFolder)) {
WriteLog "-CopyAutopilot is set to `$true, but the $AutopilotFolder folder is missing"
throw "-CopyAutopilot is set to `$true, but the $AutopilotFolder folder is missing"
}
#Check for .JSON file
if (!(Get-ChildItem -Path $AutopilotFolder -Filter *.json)) {
WriteLog "-CopyAutopilot is set to `$true, but the $AutopilotFolder folder is missing a .JSON file"
throw "-CopyAutopilot is set to `$true, but the $AutopilotFolder folder is missing a .JSON file"
}
WriteLog 'Autopilot validation complete'
}
#Validate Unattend folder
if ($CopyUnattend) {
WriteLog 'Doing Unattend validation'
if (!(Test-Path -Path $UnattendFolder)) {
WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing"
throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing"
}
#Check for .XML file
if (!(Get-ChildItem -Path $UnattendFolder -Filter unattend_*.xml)) {
WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file"
throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file"
}
WriteLog 'Unattend validation complete'
}
#Override $InstallApps value if using ESD to build FFU. This is due to a strange issue where building the FFU #Override $InstallApps value if using ESD to build FFU. This is due to a strange issue where building the FFU
#from vhdx doesn't work (you get an older style OOBE screen and get stuck in an OOBE reboot loop when hitting next). #from vhdx doesn't work (you get an older style OOBE screen and get stuck in an OOBE reboot loop when hitting next).
#This behavior doesn't happen with WIM files. #This behavior doesn't happen with WIM files.
@@ -3466,6 +3695,25 @@ if (($InstallApps -and ($VMHostIPAddress -eq ''))) {
throw "If variable InstallApps is set to `$true, VMHostIPAddress must also be set to capture the FFU. Please set -VMHostIPAddress and try again." throw "If variable InstallApps is set to `$true, VMHostIPAddress must also be set to capture the FFU. Please set -VMHostIPAddress and try again."
} }
if (($VMHostIPAddress) -and ($VMSwitchName)){
WriteLog "Validating -VMSwitchName $VMSwitchName and -VMHostIPAddress $VMHostIPAddress"
#Check $VMSwitchName by using Get-VMSwitch
$VMSwitch = Get-VMSwitch -Name $VMSwitchName -ErrorAction SilentlyContinue
if (-not $VMSwitch) {
throw "-VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again."
}
#Find the IP address of $VMSwitch and check if it matches $VMHostIPAddress
$interfaceAlias = "vEthernet ($VMSwitchName)"
$VMSwitchIPAddress = (Get-NetIPAddress -InterfaceAlias $interfaceAlias -AddressFamily 'IPv4' -ErrorAction SilentlyContinue).IPAddress
if (-not $VMSwitchIPAddress) {
throw "IP address for -VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again."
}
if ($VMSwitchIPAddress -ne $VMHostIPAddress) {
throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again."
}
WriteLog '-VMSwitchName and -VMHostIPAddress validation complete'
}
if (-not ($ISOPath) -and ($OptionalFeatures -like '*netfx3*')) { if (-not ($ISOPath) -and ($OptionalFeatures -like '*netfx3*')) {
throw "netfx3 specified as an optional feature, however Windows ISO isn't defined. Unable to get netfx3 source files from downloaded ESD media. Please specify a Windows ISO in the ISOPath parameter." throw "netfx3 specified as an optional feature, however Windows ISO isn't defined. Unable to get netfx3 source files from downloaded ESD media. Please specify a Windows ISO in the ISOPath parameter."
} }
@@ -3492,10 +3740,8 @@ if (($WindowsArch -eq 'ARM64') -and ($UpdateOneDrive -eq $true)) {
$UpdateOneDrive = $false $UpdateOneDrive = $false
WriteLog 'OneDrive currently fails to install on ARM64 VMs (even with the OneDrive ARM setup files). Setting UpdateOneDrive to false' WriteLog 'OneDrive currently fails to install on ARM64 VMs (even with the OneDrive ARM setup files). Setting UpdateOneDrive to false'
} }
# if(($WindowsArch -eq 'ARM64') -and ($UpdateLatestDefender -eq $true)){
# $UpdateLatestDefender = $false ###END PARAMETER VALIDATION
# WriteLog 'Defender ARM and x64 updates currently fail to install on ARM64 VMs. Setting UpdateLatestDefender to false'
# }
#Get script variable values #Get script variable values
LogVariableValues LogVariableValues
@@ -3599,17 +3845,20 @@ if ($InstallApps) {
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
WriteLog "Update complete" WriteLog "Update complete"
#Get Windows Security platform update ###### 9/4/2024 - Windows Security Platform update is no longer available from Update Catalog. Will change to using
$Name = "Windows Security platform definition updates" ###### https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $DefenderPath"
$KBFilePath = Save-KB -Name $Name -Path $DefenderPath # #Get Windows Security platform update
WriteLog "Latest Security Platform Update saved to $DefenderPath\$KBFilePath" # $Name = "Windows Security platform definition updates"
#Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update # WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $DefenderPath"
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update" # $KBFilePath = Save-KB -Name $Name -Path $DefenderPath
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" # WriteLog "Latest Security Platform Update saved to $DefenderPath\$KBFilePath"
$UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Security Platform Update)', ("REM Install Windows Security Platform Update`r`nd:\Defender\$KBFilePath") # #Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent # WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update"
WriteLog "Update complete" # $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"
@@ -3668,7 +3917,7 @@ if ($InstallApps) {
#Modify InstallAppsandSysprep.cmd to add in $OneDrivePath on the line after REM Install Defender Definitions #Modify InstallAppsandSysprep.cmd to add in $OneDrivePath on the line after REM Install Defender Definitions
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include OneDrive client" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include OneDrive client"
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
$UpdatedcmdContent = $CmdContent -replace '^(REM Install OneDrive Per Machine)', ("REM Install OneDrive Per Machine`r`nd:\OneDrive\OneDriveSetup.exe /allusers") $UpdatedcmdContent = $CmdContent -replace '^(REM Install OneDrive Per Machine)', ("REM Install OneDrive Per Machine`r`nd:\OneDrive\OneDriveSetup.exe /allusers /silent")
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
WriteLog "Update complete" WriteLog "Update complete"
} }
@@ -3749,10 +3998,10 @@ try {
Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1]
#Update latest Cumulative Update #Update latest Cumulative Update if both $UpdateLatestCU is $true and $UpdatePreviewCU is $false
#Changed to use MU Catalog instead of using Get-LatestWindowsKB #Changed to use MU Catalog instead of using Get-LatestWindowsKB
#The Windows release info page is updated later than the MU Catalog #The Windows release info page is updated later than the MU Catalog
if ($UpdateLatestCU) { if ($UpdateLatestCU -and -not $UpdatePreviewCU) {
Writelog "`$UpdateLatestCU is set to true, checking for latest CU" Writelog "`$UpdateLatestCU is set to true, checking for latest CU"
$Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
#Check if $KBPath exists, if not, create it #Check if $KBPath exists, if not, create it
@@ -3765,6 +4014,20 @@ try {
WriteLog "Latest CU saved to $KBPath\$KBFilePath" WriteLog "Latest CU saved to $KBPath\$KBFilePath"
} }
#Update Latest Preview Cumlative Update
#will take Precendence over $UpdateLastestCU if both were set to $true
if ($UpdatePreviewCU) {
Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU"
$Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
#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 Preview CU saved to $KBPath\$KBFilePath"
}
#Update Latest .NET Framework #Update Latest .NET Framework
if ($UpdateLatestNet) { if ($UpdateLatestNet) {
@@ -3779,23 +4042,23 @@ 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 # #Update Latest Security Platform Update
if ($UpdateSecurityPlatform) { # if ($UpdateSecurityPlatform) {
WriteLog "`$UpdateSecurityPlatform is set to true, checking for latest Security Platform Update" # WriteLog "`$UpdateSecurityPlatform is set to true, checking for latest Security Platform Update"
$Name = "Windows Security platform definition updates" # $Name = "Windows Security platform definition updates"
#Check if $KBPath exists, if not, create it # #Check if $KBPath exists, if not, create it
If (-not (Test-Path -Path $KBPath)) { # If (-not (Test-Path -Path $KBPath)) {
WriteLog "Creating $KBPath" # WriteLog "Creating $KBPath"
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null # New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
} # }
WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $KBPath" # WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $KBPath"
$KBFilePath = Save-KB -Name $Name -Path $KBPath # $KBFilePath = Save-KB -Name $Name -Path $KBPath
WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath" # WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath"
} # }
#Add Windows packages #Add Windows packages
if ($UpdateLatestCU -or $UpdateLatestNet) { if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) {
try { try {
WriteLog "Adding KBs to $WindowsPartition" WriteLog "Adding KBs to $WindowsPartition"
WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient'
@@ -3999,30 +4262,30 @@ catch {
throw $_ throw $_
} }
#Clean up InstallAppsandSysprep.cmd # #Clean up InstallAppsandSysprep.cmd
try { # try {
WriteLog "Cleaning up $AppsPath\InstallAppsandSysprep.cmd" # WriteLog "Cleaning up $AppsPath\InstallAppsandSysprep.cmd"
Clear-InstallAppsandSysprep # Clear-InstallAppsandSysprep
} # }
catch { # catch {
Write-Host 'Cleaning up InstallAppsandSysprep.cmd failed' # Write-Host 'Cleaning up InstallAppsandSysprep.cmd failed'
Writelog "Cleaning up InstallAppsandSysprep.cmd failed with error $_" # Writelog "Cleaning up InstallAppsandSysprep.cmd failed with error $_"
throw $_ # throw $_
} # }
try { # try {
if (Test-Path -Path "$AppsPath\Win32" -PathType Container) { # if (Test-Path -Path "$AppsPath\Win32" -PathType Container) {
WriteLog "Cleaning up Win32 folder" # WriteLog "Cleaning up Win32 folder"
Remove-Item -Path "$AppsPath\Win32" -Recurse -Force # Remove-Item -Path "$AppsPath\Win32" -Recurse -Force
} # }
if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) { # if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) {
WriteLog "Cleaning up MSStore folder" # WriteLog "Cleaning up MSStore folder"
Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force # Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force
} # }
} # }
catch { # catch {
WriteLog "$_" # WriteLog "$_"
throw $_ # throw $_
} # }
#Create Deployment Media #Create Deployment Media
If ($CreateDeploymentMedia) { If ($CreateDeploymentMedia) {
try { try {
@@ -4127,10 +4390,16 @@ Write-Host "FFU build process completed at" $endTime
# Calculate the total run time # Calculate the total run time
$runTime = $endTime - $startTime $runTime = $endTime - $startTime
# Format the runtime as minutes and seconds # Format the runtime with hours, minutes, and seconds
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime if ($runTime.TotalHours -ge 1) {
$runTimeFormatted = 'Duration: {0:hh} hr {0:mm} min {0:ss} sec' -f $runTime
}
else {
$runTimeFormatted = 'Duration: {0:mm} min {0:ss} sec' -f $runTime
}
if ($VerbosePreference -ne 'Continue'){ if ($VerbosePreference -ne 'Continue'){
Write-Host $runTimeFormatted Write-Host $runTimeFormatted
} }
WriteLog 'Script complete: ' + $runTimeFormatted WriteLog 'Script complete'
WriteLog $runTimeFormatted
Binary file not shown.
@@ -128,13 +128,18 @@ $LogFileName = 'ScriptLog.txt'
$USBDrive = Get-USBDrive $USBDrive = Get-USBDrive
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
$LogFile = $USBDrive + $LogFilename $LogFile = $USBDrive + $LogFilename
$version = '2408.1' $version = '2409.1'
WriteLog 'Begin Logging' WriteLog 'Begin Logging'
WriteLog "Script version: $version" WriteLog "Script version: $version"
#Find PhysicalDrive #Find PhysicalDrive
# $PhysicalDeviceID = Get-HardDrive # $PhysicalDeviceID = Get-HardDrive
$hardDrive = Get-HardDrive $hardDrive = Get-HardDrive
if($hardDrive -eq $null){
WriteLog 'No hard drive found. Exiting'
WriteLog 'Try adding storage drivers to the PE boot image (you can re-create your FFU and USB drive and add the PE drivers to the PEDrivers folder and add -CopyPEDrivers $true to the command line, or manually add them via DISM)'
Exit
}
$PhysicalDeviceID = $hardDrive.DeviceID $PhysicalDeviceID = $hardDrive.DeviceID
$BytesPerSector = $hardDrive.BytesPerSector $BytesPerSector = $hardDrive.BytesPerSector
WriteLog "Physical BytesPerSector is $BytesPerSector" WriteLog "Physical BytesPerSector is $BytesPerSector"
@@ -549,6 +554,12 @@ If (Test-Path -Path $Drivers)
WriteLog 'Copying drivers succeeded' WriteLog 'Copying drivers succeeded'
} }
WriteLog "Setting Windows Boot Manager to be first in the display order."
Invoke-Process bcdedit.exe "/set {fwbootmgr} displayorder {bootmgr} /addfirst"
WriteLog "Windows Boot Manager has been set to be first in the display order."
WriteLog "Setting default Windows boot loader to be first in the display order."
Invoke-Process bcdedit.exe "/set {bootmgr} displayorder {default} /addfirst"
WriteLog "The default Windows boot loader has been set to be first in the display order."
#Copy DISM log to USBDrive #Copy DISM log to USBDrive
WriteLog "Copying dism log to $USBDrive" WriteLog "Copying dism log to $USBDrive"
invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y"
+1 -1
View File
@@ -20,7 +20,7 @@ The Full-Flash update (FFU) process can automatically download the latest releas
# Updates # Updates
2408.1 has been released! Check out the changes in the [Change Log](ChangeLog.md) 2409.1 has been released! Check out the changes in the [Change Log](ChangeLog.md)
# Getting Started # Getting Started