From 3d13774ee456c2e1c7f76dbeed05e47c1ff2e035 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Sat, 29 Jun 2024 21:28:33 -0700 Subject: [PATCH 01/43] initial arm64 support --- FFUDevelopment/BuildFFUVM.ps1 | 52 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index a011326..13c686e 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1533,7 +1533,7 @@ function Get-WindowsESD { [int]$WindowsRelease, [Parameter(Mandatory = $false)] - [ValidateSet('x86', 'x64')] + [ValidateSet('x86', 'x64', 'ARM64')] [string]$WindowsArch, [Parameter(Mandatory = $false)] @@ -2224,7 +2224,12 @@ function New-PEMedia { } WriteLog "Copying WinPE files to $WinPEFFUPath" - & cmd /c """$DandIEnv"" && copype amd64 $WinPEFFUPath" | Out-Null + if($WindowsArch -eq 'x64') { + & cmd /c """$DandIEnv"" && copype amd64 $WinPEFFUPath" | Out-Null + } + elseif($WindowsArch -eq 'arm64') { + & cmd /c """$DandIEnv"" && copype arm64 $WinPEFFUPath" | Out-Null + } #Invoke-Process cmd "/c ""$DandIEnv"" && copype amd64 $WinPEFFUPath" WriteLog 'Files copied successfully' @@ -2247,7 +2252,13 @@ function New-PEMedia { "en-us\WinPE-DismCmdlets_en-us.cab" ) - $PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\" + if($WindowsArch -eq 'x64'){ + $PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\" + } + elseif($WindowsArch -eq 'arm64'){ + $PackagePathBase = "$adkPath`Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\" + } + foreach ($Package in $Packages) { $PackagePath = Join-Path $PackagePathBase $Package @@ -2260,7 +2271,7 @@ function New-PEMedia { Copy-Item -Path "$FFUDevelopmentPath\WinPECaptureFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force | out-null WriteLog "Copy complete" #Remove Bootfix.bin - for BIOS systems, shouldn't be needed, but doesn't hurt to remove for our purposes - Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null + #Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null $WinPEISOName = 'WinPE_FFU_Capture.iso' $Capture = $false } @@ -2286,11 +2297,23 @@ function New-PEMedia { Dismount-WindowsImage -Path "$WinPEFFUPath\mount" -Save | Out-Null WriteLog 'Dismount complete' #Make ISO - $OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg" + if ($WindowsArch -eq 'x64') { + $OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg" + } + elseif ($WindowsArch -eq 'arm64') { + $OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\arm64\Oscdimg" + } $OSCDIMG = "$OSCDIMGPath\oscdimg.exe" WriteLog "Creating WinPE ISO at $FFUDevelopmentPath\$WinPEISOName" # & "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\$WinPEISOName | Out-null - Invoke-Process $OSCDIMG "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$FFUDevelopmentPath\$WinPEISOName`"" + if($WindowsArch -eq 'x64'){ + $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$FFUDevelopmentPath\$WinPEISOName`"" + + } + elseif($WindowsArch -eq 'arm64'){ + $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$FFUDevelopmentPath\$WinPEISOName`"" + } + Invoke-Process $OSCDIMG $OSCDIMGArgs WriteLog "ISO created successfully" WriteLog "Cleaning up $WinPEFFUPath" Remove-Item -Path "$WinPEFFUPath" -Recurse -Force @@ -2998,6 +3021,7 @@ if ($InstallApps) { $DefenderDefURL = 'https://go.microsoft.com/fwlink/?LinkID=121721&arch=arm' } try { + WriteLog "Defender definitions URL is $DefenderDefURL" Start-BitsTransferWithRetry -Source $DefenderDefURL -Destination "$DefenderPath\mpam-fe.exe" WriteLog "Defender Definitions downloaded to $DefenderPath\mpam-fe.exe" } @@ -3023,7 +3047,14 @@ if ($InstallApps) { New-Item -Path $OneDrivePath -ItemType Directory -Force | Out-Null } WriteLog "Downloading latest OneDrive client" - $OneDriveURL = 'https://go.microsoft.com/fwlink/?linkid=844652' + if($WindowsArch -eq 'x64') + { + $OneDriveURL = 'https://go.microsoft.com/fwlink/?linkid=844652' + } + elseif($WindowsArch -eq 'ARM64') + { + $OneDriveURL = 'https://go.microsoft.com/fwlink/?linkid=2271260' + } try { Start-BitsTransferWithRetry -Source $OneDriveURL -Destination "$OneDrivePath\OneDriveSetup.exe" WriteLog "OneDrive client downloaded to $OneDrivePath\OneDriveSetup.exe" @@ -3205,7 +3236,12 @@ try { #Copy Unattend file so VM Boots into Audit Mode WriteLog 'Copying unattend file to boot to audit mode' New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory | Out-Null - Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null + if($WindowsArch -eq 'x64'){ + Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_x64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null + } + else { + Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_arm64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null + } WriteLog 'Copy completed' Dismount-ScratchVhdx -VhdxPath $VHDXPath } From 6cfe41f963ce94f81aee16ddce610480bf2c4e3f Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:31:40 -0400 Subject: [PATCH 02/43] Add Install-WinGet function --- FFUDevelopment/BuildFFUVM.ps1 | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index a011326..14c5fc5 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1657,6 +1657,43 @@ function Get-Office { Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $content } } + +function Install-WinGet { + param ( + [bool]$InstallWithDependencies + ) + $wingetPreviewLink = "https://aka.ms/getwingetpreview" + $wingetPackageDestination = "$env:TEMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" + if ($InstallWithDependencies) { + $dependencies = @( + @{ + Source = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx" + Destination = "$env:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx" + }, + @{ + Source = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx" + Destination = "$env:TEMP\Microsoft.UI.Xaml.2.8.x64.appx" + } + ) + Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination + foreach ($dependency in $dependencies) { + Start-BitsTransferWithRetry -Source $dependency.Source -Destination $dependency.Destination + Add-AppxPackage -Path $dependency.Destination + Remove-Item -Path $dependency.Destination -Force -ErrorAction SilentlyContinue + } + Add-AppxPackage -Path $wingetPackageDestination + Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue + } + else { + # If WinGet was already installed, then installing the dependencies can cause an error if the system has a newer version of the dependencies than the ones downloaded. + WriteLog "Downloading WinGet..." + Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination + WriteLog "Installing WinGet..." + Add-AppxPackage -Path $wingetPackageDestination + WriteLog "Removing WinGet installer..." + Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue + } +} function Get-KBLink { param( [Parameter(Mandatory)] From 45a2c0c29de1975764cad00dc23839a5ff447a26 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:32:18 -0400 Subject: [PATCH 03/43] Add New-WinGetSettings function --- FFUDevelopment/BuildFFUVM.ps1 | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 14c5fc5..ec17183 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1694,6 +1694,46 @@ function Install-WinGet { Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue } } + +function New-WinGetSettings { + $wingetSettingsFile = "$env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json" + $wingetSettings = @( + '{' + ' "$schema": "https://aka.ms/winget-settings.schema.json",' + ' ' + ' // For documentation on these settings, see: https://aka.ms/winget-settings' + ' "experimentalFeatures": {' + ' "storeDownload": true' + ' },' + ' "logging": {' + ' "level": "verbose"' + ' }' + '}' + ) + $wingetSettingsContent = $wingetSettings -join "`n" + if (Test-Path -Path $wingetSettingsFile -PathType Leaf) { + $jsonContent = Get-Content -Path $wingetSettingsFile -Raw + # Check if storeDownload feature is already enabled + if ($jsonContent -notmatch '"storeDownload"\s*:\s*true') { + # Back up existing settings.json file + $backupWingetSettingsFile = $wingetSettingsFile + ".bak" + if (-not (Test-Path -Path $backupWingetSettingsFile -PathType Leaf)) { + WriteLog "Backing up existing WinGet settings.json file to $backupWingetSettingsFile" + Copy-Item -Path $wingetSettingsFile -Destination $backupWingetSettingsFile -Force | Out-Null + } + WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" + $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force + } + else { + WriteLog "WinGet's settings.json file is already configured to enable the storeDownload feature." + } + } + else { + WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" + $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force + } +} + function Get-KBLink { param( [Parameter(Mandatory)] From 36f5350f12587a2d98929359bb991edf03726081 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:32:54 -0400 Subject: [PATCH 04/43] Add Get-Win32App function --- FFUDevelopment/BuildFFUVM.ps1 | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index ec17183..3a2174a 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1734,6 +1734,51 @@ function New-WinGetSettings { } } +function Get-Win32App { + param ( + [string]$Win32App, + [int]$LineNumber + ) + $wingetSearchResult = & winget.exe search --name "$Win32App" --exact --accept-source-agreements --source winget + if ($wingetSearchResult -contains "No package found matching input criteria.") { + WriteLog "$Win32App not found in WinGet repository. Skipping download." + return + } + $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $Win32App + New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null + $appFolder = Split-Path -Path $appFolderPath -Leaf + WriteLog "Downloading $Win32App..." + $wingetDownloadResult = & winget.exe download --name "$Win32App" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + if ($wingetDownloadResult -notmatch "Installer downloaded") { + WriteLog "$Win32App did not successfully download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } + WriteLog "$Win32App has completed downloading." + $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include *.exe, *.msi -File + $installer = Split-Path -Path $installerPath -Leaf + $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include *.yaml -File + $yamlContent = Get-Content -Path $yamlFile -Raw + $silentInstallSwitch = [regex]::Match($yamlContent, 'Silent:\s*(.+)').Groups[1].Value + if (-not $silentInstallSwitch) { + WriteLog "Silent install switch for $Win32App could not be found. Skipping the inclusion of $Win32App." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } + $installerFileExtension = [System.IO.Path]::GetExtension($installer) + if ($installerFileExtension -eq ".exe") { + $silentInstallCommand = "`"D:\win32\$appFolder\$installer`" $silentInstallSwitch" + } + elseif ($installerFileExtension -eq ".msi") { + $silentInstallCommand = "msiexec /i `"D:\win32\$appFolder\$installer`" $silentInstallSwitch" + } + $cmdFile = "$AppsPath\InstallAppsandSysprep.cmd" + $cmdContent = Get-Content -Path $cmdFile + $cmdContent = $cmdContent[0..($lineNumber - 2)] + $silentInstallCommand.Trim() + $cmdContent[($lineNumber - 1)..($cmdContent.Length - 1)] + WriteLog "Writing silent install command for $Win32App to InstallAppsandSysprep.cmd at line number $LineNumber" + Set-Content -Path $cmdFile -Value $cmdContent +} + function Get-KBLink { param( [Parameter(Mandatory)] From 5c77b171f16a31d6fd29be17cbc190a444295ecd Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:33:26 -0400 Subject: [PATCH 05/43] Add Get-StoreApp function --- FFUDevelopment/BuildFFUVM.ps1 | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3a2174a..4323458 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1779,6 +1779,64 @@ function Get-Win32App { Set-Content -Path $cmdFile -Value $cmdContent } +function Get-StoreApp { + param ( + [string]$StoreApp + ) + $wingetSearchResult = & winget.exe search --name --exact "$StoreApp" --accept-source-agreements --source msstore + if ($wingetSearchResult -contains "No package found matching input criteria.") { + WriteLog "$StoreApp not found in WinGet repository. Skipping download." + return + } + $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreApp + New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null + # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. + # WinGet's download command will return a non-zero exit code when downloading store apps, as attempting to download the license file always appears to cause an error. + WriteLog "Downloading $StoreApp and dependencies..." + $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore | Out-String + # Many store apps can be found by winget search, but the download of the apps are unsupported. + if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { + WriteLog "No applicable Microsoft Store package download information found for $StoreApp. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } + $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { + WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." + $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent + } + WriteLog "$StoreApp has completed downloading. Identifying the latest version of $StoreApp." + $packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*" -File + # 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 = "" + $latestDate = [datetime]::MinValue + foreach ($package in $packages) { + $signature = Get-AuthenticodeSignature -FilePath $package.FullName + if ($signature.Status -eq 'Valid') { + $signatureDate = $signature.SignerCertificate.NotBefore + if ($signatureDate -gt $latestDate) { + $latestPackage = $package.FullName + $latestDate = $signatureDate + } + } + } + # Removing all packages that are not the latest version + WriteLog "Latest version of $StoreApp has been identified as $latestPackage. Removing old versions of $StoreApp 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-KBLink { param( [Parameter(Mandatory)] From cafff0b4843ca8da3b3f636da0cd5f23b3c5abb5 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:34:03 -0400 Subject: [PATCH 06/43] Add Get-Apps function --- FFUDevelopment/BuildFFUVM.ps1 | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 4323458..3480827 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1837,6 +1837,76 @@ function Get-StoreApp { } } +function Get-Apps { + param ( + [string]$AppsList + ) + $apps = Get-Content -Path $AppsList + if (-not $apps) { + WriteLog "No apps were specified in AppsList.txt file." + return + } + $win32Apps = @() + $storeApps = @() + $apps | ForEach-Object { + if ($_ -like 'win32:*') { + $win32Apps += $_.Substring(6) + } + elseif ($_ -like 'store:*') { + $storeApps += $_.Substring(6) + } + } + $wingetInstalled = Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" + if (-not $wingetInstalled) { + WriteLog "WinGet is not installed. Downloading preview version of WinGet and its dependencies..." + Install-WinGet -InstallWithDependencies $true + } + $wingetOnPath = Get-Command winget -ErrorAction SilentlyContinue + if (-not $wingetOnPath) { + WriteLog "WinGet is not on the path. Downloading preview version of WinGet without dependencies..." + Install-WinGet -InstallWithDependencies $false + } + $wingetVersion = & winget.exe --version + # Preview release is needed to enable storeDownload experimental feature + if (-not ($wingetVersion -like "*preview*")) { + WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet without dependencies..." + Install-WinGet -InstallWithDependencies $false + } + $lineNumber = 13 + $win32Folder = Join-Path -Path $AppsPath -ChildPath "Win32" + $storeAppsFolder = Join-Path -Path $AppsPath -ChildPath "MSStore" + if ($win32Apps) { + if (-not (Test-Path -Path $win32Folder -PathType Container)) { + New-Item -Path $win32Folder -ItemType Directory -Force | Out-Null + } + foreach ($win32App in $win32Apps) { + try { + Get-Win32App -Win32App $win32App -LineNumber $lineNumber + $lineNumber++ + } + catch { + WriteLog "Error occurred while processing $win32App : $_" + throw $_ + } + } + } + if ($storeApps) { + New-WinGetSettings + if (-not (Test-Path -Path $storeAppsFolder -PathType Container)) { + New-Item -Path $storeAppsFolder -ItemType Directory -Force | Out-Null + } + foreach ($storeApp in $storeApps) { + try { + Get-StoreApp -StoreApp $storeApp + } + catch { + WriteLog "Error occurred while processing $storeApp : $_" + throw $_ + } + } + } +} + function Get-KBLink { param( [Parameter(Mandatory)] From 1729eaddd6a17035b3bc6ed452b649a79c30bbcb Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:42:06 -0400 Subject: [PATCH 07/43] Add AppsList, modified InstallAppsandSysprep.cmd file with store apps installation, and updated Clear-InstallAppsandSysprep function --- FFUDevelopment/Apps/AppsList.txt | 2 ++ FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 30 +++++++++++++++++++ FFUDevelopment/BuildFFUVM.ps1 | 6 ++++ 3 files changed, 38 insertions(+) create mode 100644 FFUDevelopment/Apps/AppsList.txt diff --git a/FFUDevelopment/Apps/AppsList.txt b/FFUDevelopment/Apps/AppsList.txt new file mode 100644 index 0000000..6578766 --- /dev/null +++ b/FFUDevelopment/Apps/AppsList.txt @@ -0,0 +1,2 @@ +win32:7-Zip +store:Company Portal \ No newline at end of file diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index de29459..205d4c4 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -1,3 +1,4 @@ +setlocal enabledelayedexpansion REM Put each app install on a separate line REM M365 Apps/Office ProPlus REM d:\Office\setup.exe /configure d:\office\DeployFFU.xml @@ -9,6 +10,35 @@ REM Install Edge Stable REM Add additional apps below here REM Contoso App (Example) REM msiexec /i d:\Contoso\setup.msi /qn /norestart +set "INSTALL_STOREAPPS=false" +if /i "%INSTALL_STOREAPPS%"=="false" ( + echo Skipping MS Store installation due to INSTALL_STOREAPPS flag. + goto :remaining +) +set "basepath=D:\MSStore" +for /d %%D in ("%basepath%\*") do ( + set "appfolder=%%D" + set "mainpackage=" + set "dependenciesfolder=!appfolder!\Dependencies" + for %%F in ("!appfolder!\*") do ( + if not "%%~dpF"=="!dependenciesfolder!\" ( + set "mainpackage=%%F" + ) + ) + if defined mainpackage ( + if exist "!dependenciesfolder!" ( + set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!"" + for %%G in ("!dependenciesfolder!\*") do ( + set "dism_command=!dism_command! /DependencyPackagePath:"%%G"" + ) + set "dism_command=!dism_command! /SkipLicense /Region:All" + echo !dism_command! + !dism_command! + ) + ) +) +:remaining +endlocal REM The below lines will remove the unattend.xml that gets the machine into audit mode. If not removed, the OS will get stuck booting to audit mode each time. REM Also kills the sysprep process in order to automate sysprep generalize del c:\windows\panther\unattend\unattend.xml /F /Q diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3480827..09fdd78 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3050,6 +3050,12 @@ function Remove-FFU { WriteLog "Removal complete" } function Clear-InstallAppsandSysprep { + $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove win32 app install commands" + $cmdContent -notmatch "D:\\win32*" | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + WriteLog "Setting MSStore installation condition to false" + $cmdContent -replace 'set "INSTALL_STOREAPPS=true"', 'set "INSTALL_STOREAPPS=false"' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" if ($UpdateLatestDefender) { WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Defender Platform Update" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" From 205b58aaa7c354d367e0158e8beceefedaea5efb Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 19:45:43 -0400 Subject: [PATCH 08/43] Added cleanup of Win32 and MSStore folders --- FFUDevelopment/BuildFFUVM.ps1 | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 09fdd78..126ea4c 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3037,8 +3037,16 @@ function Get-FFUEnvironment { WriteLog "Removing $EdgePath" Remove-Item -Path $EdgePath -Recurse -Force WriteLog 'Removal complete' - } - + } + if (Test-Path -Path "$AppsPath\Win32" -PathType Container) { + WriteLog "Cleaning up Win32 folder" + Remove-Item -Path "$AppsPath\Win32" -Recurse -Force + } + if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) { + WriteLog "Cleaning up MSStore folder" + Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force + } + Clear-InstallAppsandSysprep Writelog 'Removing dirty.txt file' Remove-Item -Path "$FFUDevelopmentPath\dirty.txt" -Force WriteLog "Cleanup complete" @@ -3600,6 +3608,20 @@ catch { Writelog "Cleaning up InstallAppsandSysprep.cmd failed with error $_" throw $_ } +try { + if (Test-Path -Path "$AppsPath\Win32" -PathType Container) { + WriteLog "Cleaning up Win32 folder" + Remove-Item -Path "$AppsPath\Win32" -Recurse -Force + } + if (Test-Path -Path "$AppsPath\MSStore" -PathType Container) { + WriteLog "Cleaning up MSStore folder" + Remove-Item -Path "$AppsPath\MSStore" -Recurse -Force + } +} +catch { + WriteLog "$_" + throw $_ +} #Create Deployment Media If ($CreateDeploymentMedia) { try { From 4af808c939262213a0e5d73ce437b724eb2de613 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 30 Jun 2024 20:15:13 -0400 Subject: [PATCH 09/43] Invoke Get-Apps function with AppList.txt argument --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 126ea4c..e4d99f3 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3206,7 +3206,7 @@ if ($InstallApps) { exit } WriteLog "$AppsPath\InstallAppsandSysprep.cmd found" - + Get-Apps -AppsList "$AppsPath\AppsList.txt" if (-not $InstallOffice) { #Modify InstallAppsandSysprep.cmd to REM out the office install command $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" From 7a0dd3435c1e7094aa30760e42be5c9aa511a83c Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Mon, 1 Jul 2024 10:09:11 -0700 Subject: [PATCH 10/43] Fixed a logic issue when downloading ARM KBs --- FFUDevelopment/BuildFFUVM.ps1 | 167 +++++++++++++----- .../WinPEDeployFFUFiles/ApplyFFU.ps1 | 2 +- 2 files changed, 125 insertions(+), 44 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 13c686e..e508c57 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -310,7 +310,7 @@ param( "Upgrade-Insecure-Requests" = "1" } ) -$version = '2406.1' +$version = '2407.1' #Check if Hyper-V feature is installed (requires only checks the module) $osInfo = Get-WmiObject -Class Win32_OperatingSystem @@ -1748,57 +1748,120 @@ function Save-KB { [array]$WindowsArch = @("x64", "amd64") } + # 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) { $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 - if ($WindowsArch -eq 'x64'){ - if ($link -match 'securityhealthsetup_e1'){ - Start-BitsTransferWithRetry -Source $link -Destination $Path - $fileName = ($link -split '/')[-1] - break - } + if (!($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 + } + } + 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 } - elseif ($WindowsArch -eq 'arm64'){ - if ($link -match 'securityhealthsetup_25'){ - Start-BitsTransferWithRetry -Source $link -Destination $Path - $fileName = ($link -split '/')[-1] - break - } - } - continue } - Start-BitsTransferWithRetry -Source $link -Destination $Path - $fileName = ($link -split '/')[-1] } } - else { - if ($link -match $WindowsArch) { + + if ($link -match 'x64' -or $link -match 'amd64') { + if($WindowsArch -is [array]) { + if ($link -match $WindowsArch[0] -or $link -match $WindowsArch[1]) { + Writelog "Downloading $Link for $WindowsArch to $Path" + Start-BitsTransferWithRetry -Source $link -Destination $Path + $fileName = ($link -split '/')[-1] + Writelog "Returning $fileName" + break + } + } + + } + if ($link -match 'arm64') { + if ($WindowsArch -eq 'arm64') { + Writelog "Downloading $Link for $WindowsArch to $Path" Start-BitsTransferWithRetry -Source $link -Destination $Path $fileName = ($link -split '/')[-1] + Writelog "Returning $fileName" break } } @@ -2900,6 +2963,19 @@ if (($InstallApps -eq $false) -and (($UpdateLatestDefender -eq $true) -or ($Upda WriteLog 'You have selected to update Defender, OneDrive, or Edge, however you are setting InstallApps to false. These updates require the InstallApps variable to be set to true. Please set InstallApps to true and try again.' throw "InstallApps variable must be set to `$true to update Defender, OneDrive, or Edge" } +if (($WindowsArch -eq 'ARM64') -and ($InstallOffice -eq $true)) { + $InstallOffice = $false + WriteLog 'M365 Apps/Office currently fails to install on ARM64 VMs without an internet connection. Setting InstallOffice to false' +} + +if (($WindowsArch -eq 'ARM64') -and ($UpdateOneDrive -eq $true)) { + $UpdateOneDrive = $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 +# WriteLog 'Defender ARM and x64 updates currently fail to install on ARM64 VMs. Setting UpdateLatestDefender to false' +# } #Get script variable values LogVariableValues @@ -3094,6 +3170,11 @@ if ($InstallApps) { Invoke-Process Expand "$EdgeCABFilePath -F:*.msi $EdgeFullFilePath" WriteLog "Expansion complete" + #Remove Edge CAB file + WriteLog "Removing $EdgeCABFilePath" + Remove-Item -Path $EdgeCABFilePath -Force + WriteLog "Removal complete" + #Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Edge Stable WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Edge Stable $WindowsArch release" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 index c6b9491..ec131e6 100644 --- a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -117,7 +117,7 @@ $LogFileName = 'ScriptLog.txt' $USBDrive = Get-USBDrive New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null $LogFile = $USBDrive + $LogFilename -$version = '2406.1' +$version = '2407.1' WriteLog 'Begin Logging' WriteLog "Script version: $version" From c30aa90e8b30521ba3a75782a6efc153c625214c Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Mon, 1 Jul 2024 16:22:56 -0700 Subject: [PATCH 11/43] added architecture and other minor changes for winget app downloads --- FFUDevelopment/BuildFFUVM.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 64d21e1..3f08303 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1748,7 +1748,7 @@ function Get-Win32App { New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null $appFolder = Split-Path -Path $appFolderPath -Leaf WriteLog "Downloading $Win32App..." - $wingetDownloadResult = & winget.exe download --name "$Win32App" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + $wingetDownloadResult = & winget.exe download --name "$Win32App" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String if ($wingetDownloadResult -notmatch "Installer downloaded") { WriteLog "$Win32App did not successfully download." Remove-Item -Path $appFolderPath -Recurse -Force @@ -1793,7 +1793,8 @@ function Get-StoreApp { # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. # WinGet's download command will return a non-zero exit code when downloading store apps, as attempting to download the license file always appears to cause an error. WriteLog "Downloading $StoreApp and dependencies..." - $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore | Out-String + 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.' + $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String # Many store apps can be found by winget search, but the download of the apps are unsupported. if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { WriteLog "No applicable Microsoft Store package download information found for $StoreApp. Skipping download." @@ -1807,7 +1808,7 @@ function Get-StoreApp { Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent } WriteLog "$StoreApp has completed downloading. Identifying the latest version of $StoreApp." - $packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*" -File + $packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File # 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 = "" $latestDate = [datetime]::MinValue From dd20eceb5559531bba31e5a84d59529270651bea Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Mon, 1 Jul 2024 16:23:04 -0700 Subject: [PATCH 12/43] comment change --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3f08303..7004479 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1998,7 +1998,7 @@ function Save-KB { if ($WindowsArch -eq 'x64') { [array]$WindowsArch = @("x64", "amd64") } - + #Keep for now, will remove in future # foreach ($kb in $name) { # $links = Get-KBLink -Name $kb # foreach ($link in $links) { From d16acce0abdc32b6f0d8ad365fd8438ca73d696b Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Mon, 1 Jul 2024 22:34:03 -0400 Subject: [PATCH 13/43] Add condition to use winget download command without architecture parameter if specifying it leads to no app result --- FFUDevelopment/BuildFFUVM.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 7004479..b42fa25 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1795,6 +1795,10 @@ function Get-StoreApp { WriteLog "Downloading $StoreApp and dependencies..." 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.' $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String + # For some apps, specifiying 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") { + $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + } # Many store apps can be found by winget search, but the download of the apps are unsupported. if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { WriteLog "No applicable Microsoft Store package download information found for $StoreApp. Skipping download." From 0010c8ad8194fa6573db72a27e41bebb3b146247 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Mon, 1 Jul 2024 22:39:43 -0400 Subject: [PATCH 14/43] Fix typo --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index b42fa25..ee2d362 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1795,7 +1795,7 @@ function Get-StoreApp { WriteLog "Downloading $StoreApp and dependencies..." 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.' $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String - # For some apps, specifiying the architecture leads to no results found for the app. In those cases, the command will be run without the architecture parameter. + # 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") { $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String } From 95a4664b2604d580a1c1518c17e9350e4b0189a6 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:45:14 -0400 Subject: [PATCH 15/43] Added handling of win32 apps using the msstore source in winget download command, updated InstallAppsandSysprep.cmd file to use store app license files, and updated AppList.txt with winget prefix, instead of win32 --- FFUDevelopment/Apps/AppsList.txt | 2 +- FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 16 ++- FFUDevelopment/BuildFFUVM.ps1 | 99 ++++++++++++------- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/FFUDevelopment/Apps/AppsList.txt b/FFUDevelopment/Apps/AppsList.txt index 6578766..b4903ae 100644 --- a/FFUDevelopment/Apps/AppsList.txt +++ b/FFUDevelopment/Apps/AppsList.txt @@ -1,2 +1,2 @@ -win32:7-Zip +winget:7-Zip store:Company Portal \ No newline at end of file diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index 205d4c4..fde1977 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -22,16 +22,28 @@ for /d %%D in ("%basepath%\*") do ( set "dependenciesfolder=!appfolder!\Dependencies" for %%F in ("!appfolder!\*") do ( if not "%%~dpF"=="!dependenciesfolder!\" ( - set "mainpackage=%%F" + if /i not "%%~xF"==".xml" ( + if /i not "%%~xF"==".yaml" ( + set "mainpackage=%%F" + ) + ) ) ) + for %%F in ("!appfolder!\*.xml") do ( + set "licensefile=%%F" + ) if defined mainpackage ( if exist "!dependenciesfolder!" ( set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!"" for %%G in ("!dependenciesfolder!\*") do ( set "dism_command=!dism_command! /DependencyPackagePath:"%%G"" ) - set "dism_command=!dism_command! /SkipLicense /Region:All" + if defined licensefile ( + set "dism_command=!dism_command! /LicensePath:"!licensefile!"" + ) else ( + set "dism_command=!dism_command! /SkipLicense" + ) + set "dism_command=!dism_command! /Region:All" echo !dism_command! !dism_command! ) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index ee2d362..9764a84 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1734,34 +1734,20 @@ function New-WinGetSettings { } } -function Get-Win32App { +function Add-Win32SilentInstallCommand { param ( - [string]$Win32App, - [int]$LineNumber + [string]$AppFolder, + [string]$AppFolderPath ) - $wingetSearchResult = & winget.exe search --name "$Win32App" --exact --accept-source-agreements --source winget - if ($wingetSearchResult -contains "No package found matching input criteria.") { - WriteLog "$Win32App not found in WinGet repository. Skipping download." - return - } - $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $Win32App - New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - $appFolder = Split-Path -Path $appFolderPath -Leaf - WriteLog "Downloading $Win32App..." - $wingetDownloadResult = & winget.exe download --name "$Win32App" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String - if ($wingetDownloadResult -notmatch "Installer downloaded") { - WriteLog "$Win32App did not successfully download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } - WriteLog "$Win32App has completed downloading." - $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include *.exe, *.msi -File + $appName = $AppFolder + $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include *.exe, *.msi -File -ErrorAction Stop $installer = Split-Path -Path $installerPath -Leaf - $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include *.yaml -File + $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include *.yaml -File -ErrorAction Stop $yamlContent = Get-Content -Path $yamlFile -Raw $silentInstallSwitch = [regex]::Match($yamlContent, 'Silent:\s*(.+)').Groups[1].Value + $silentInstallSwitch = $silentInstallSwitch.Replace("'", "").Trim() if (-not $silentInstallSwitch) { - WriteLog "Silent install switch for $Win32App could not be found. Skipping the inclusion of $Win32App." + WriteLog "Silent install switch for $appName could not be found. Skipping the inclusion of $appName." Remove-Item -Path $appFolderPath -Recurse -Force return } @@ -1775,8 +1761,35 @@ function Get-Win32App { $cmdFile = "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent = Get-Content -Path $cmdFile $cmdContent = $cmdContent[0..($lineNumber - 2)] + $silentInstallCommand.Trim() + $cmdContent[($lineNumber - 1)..($cmdContent.Length - 1)] - WriteLog "Writing silent install command for $Win32App to InstallAppsandSysprep.cmd at line number $LineNumber" - Set-Content -Path $cmdFile -Value $cmdContent + WriteLog "Writing silent install command for $appName to InstallAppsandSysprep.cmd at line number $LineNumber" + Set-Content -Path $cmdFile -Value $cmdContent +} + +function Get-WinGetApp { + param ( + [string]$WinGetApp, + [int]$LineNumber + ) + $wingetSearchResult = & winget.exe search --name "$WinGetApp" --exact --accept-source-agreements --source winget + if ($wingetSearchResult -contains "No package found matching input criteria.") { + WriteLog "$WinGetApp not found in WinGet repository. Skipping download." + return + } + $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetApp + New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null + $appFolder = Split-Path -Path $appFolderPath -Leaf + WriteLog "Downloading $WinGetApp..." + $wingetDownloadResult = & winget.exe download --name "$WinGetApp" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String + if ($wingetDownloadResult -match "No applicable installer found") { + $wingetDownloadResult = & winget.exe download --name "$WinGetApp" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + } + if ($wingetDownloadResult -notmatch "Installer downloaded") { + WriteLog "$WinGetApp did not successfully download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } + WriteLog "$WinGetApp has completed downloading." + Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath } function Get-StoreApp { @@ -1788,6 +1801,24 @@ function Get-StoreApp { WriteLog "$StoreApp not found in WinGet repository. Skipping download." return } + # Skip the header lines and get the line with the app information + $appResult = $wingetSearchResult | Select-Object -Skip 2 | Select-Object -First 1 + # Split the line by whitespace and get the second-to-last item (the Id) + $appID = ($appResult -split '\s+')[-2] + # Checking app ID to determine if store app is a win32 app + if ($appID.StartsWith("XP")) { + WriteLog "$StoreApp is a win32 app. Adding to $AppsPath\win32 folder" + $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreApp + New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null + $appFolder = Split-Path -Path $appFolderPath -Leaf + WriteLog "Downloading $StoreApp..." + $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String + if ($wingetDownloadResult -match "No applicable installer found") { + $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + } + Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath + return + } $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreApp New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. @@ -1812,7 +1843,7 @@ function Get-StoreApp { Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent } WriteLog "$StoreApp has completed downloading. Identifying the latest version of $StoreApp." - $packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File + $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 = "" $latestDate = [datetime]::MinValue @@ -1851,17 +1882,17 @@ function Get-Apps { WriteLog "No apps were specified in AppsList.txt file." return } - $win32Apps = @() + $wingetApps = @() $storeApps = @() $apps | ForEach-Object { - if ($_ -like 'win32:*') { - $win32Apps += $_.Substring(6) + if ($_ -like 'winget:*') { + $wingetApps += $_.Substring(7).Trim() } elseif ($_ -like 'store:*') { - $storeApps += $_.Substring(6) + $storeApps += $_.Substring(6).Trim() } } - $wingetInstalled = Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" + $wingetInstalled = Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" -ErrorAction SilentlyContinue if (-not $wingetInstalled) { WriteLog "WinGet is not installed. Downloading preview version of WinGet and its dependencies..." Install-WinGet -InstallWithDependencies $true @@ -1880,17 +1911,17 @@ function Get-Apps { $lineNumber = 13 $win32Folder = Join-Path -Path $AppsPath -ChildPath "Win32" $storeAppsFolder = Join-Path -Path $AppsPath -ChildPath "MSStore" - if ($win32Apps) { + if ($wingetApps) { if (-not (Test-Path -Path $win32Folder -PathType Container)) { New-Item -Path $win32Folder -ItemType Directory -Force | Out-Null } - foreach ($win32App in $win32Apps) { + foreach ($wingetApp in $wingetApps) { try { - Get-Win32App -Win32App $win32App -LineNumber $lineNumber + Get-WinGetApp -WinGetApp $wingetApp -LineNumber $lineNumber $lineNumber++ } catch { - WriteLog "Error occurred while processing $win32App : $_" + WriteLog "Error occurred while processing $wingetApp : $_" throw $_ } } From 2423814cc25892c7bfa1f904f640cab04e56b515 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Wed, 3 Jul 2024 16:30:12 -0700 Subject: [PATCH 16/43] changed unattend files for naming the PC for arm64 and modified arm64 store app download logic for dependency handling --- FFUDevelopment/BuildFFUVM.ps1 | 50 +++++++++++++++++-- FFUDevelopment/unattend/unattend_arm64.xml | 8 +++ .../{unattend.xml => unattend_x64.xml} | 0 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 FFUDevelopment/unattend/unattend_arm64.xml rename FFUDevelopment/unattend/{unattend.xml => unattend_x64.xml} (100%) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 9764a84..ef6da11 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1806,15 +1806,25 @@ function Get-StoreApp { # Split the line by whitespace and get the second-to-last item (the Id) $appID = ($appResult -split '\s+')[-2] # Checking app ID to determine if store app is a win32 app + WriteLog "Checking if $StoreApp is a win32 app..." if ($appID.StartsWith("XP")) { WriteLog "$StoreApp is a win32 app. Adding to $AppsPath\win32 folder" $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreApp New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null $appFolder = Split-Path -Path $appFolderPath -Leaf - WriteLog "Downloading $StoreApp..." + WriteLog "Downloading $StoreApp for $WindowsArch architecture..." $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String if ($wingetDownloadResult -match "No applicable installer found") { + WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + if ($wingetDownloadResult -match $StoreApp){ + WriteLog "Downloaded $StoreApp without specifying architecture." + } + else { + WriteLog "No installer found for $StoreApp. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } } Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath return @@ -1828,7 +1838,29 @@ function Get-StoreApp { $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | 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..." $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + if ($wingetDownloadResult -match $StoreApp){ + WriteLog "Downloaded $StoreApp without specifying architecture." + # 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 + } + } + } + } + } + else { + WriteLog "No installer found for $StoreApp. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } } # Many store apps can be found by winget search, but the download of the apps are unsupported. if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { @@ -3012,10 +3044,20 @@ Function New-DeploymentUSB { } } - #Copy Unattend folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder + #Copy Unattend file to the USB drive. if ($CopyUnattend) { - WriteLog "Copying Unattend folder to $DeployPartitionDriveLetter" - Copy-Item -Path "$FFUDevelopmentPath\Unattend" -Destination $DeployPartitionDriveLetter -Recurse -Force + # WriteLog "Copying Unattend folder to $DeployPartitionDriveLetter" + # Copy-Item -Path "$FFUDevelopmentPath\Unattend" -Destination $DeployPartitionDriveLetter -Recurse -Force + $DeployUnattendPath = "$DeployPartitionDriveLetter\unattend" + WriteLog "Copying unattend file to $DeployUnattendPath" + New-Item -Path $DeployUnattendPath -ItemType Directory | Out-Null + if ($WindowsArch -eq 'x64') { + Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_x64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null + } + else { + Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_arm64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null + } + WriteLog 'Copy completed' } #Copy PPKG folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder if ($CopyPPKG) { diff --git a/FFUDevelopment/unattend/unattend_arm64.xml b/FFUDevelopment/unattend/unattend_arm64.xml new file mode 100644 index 0000000..98b1e7f --- /dev/null +++ b/FFUDevelopment/unattend/unattend_arm64.xml @@ -0,0 +1,8 @@ + + + + + MyComputer + + + \ No newline at end of file diff --git a/FFUDevelopment/unattend/unattend.xml b/FFUDevelopment/unattend/unattend_x64.xml similarity index 100% rename from FFUDevelopment/unattend/unattend.xml rename to FFUDevelopment/unattend/unattend_x64.xml From 8c897e93fe5fee62cd6b7dfa5f4f06c6841bc6aa Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:57:14 -0400 Subject: [PATCH 17/43] Added support for AppList to be in JSON format. WinGet searches now use app ID. Modified InstallAppsandSysprep.cmd to handle packages with no dependencies --- FFUDevelopment/Apps/AppList.json | 14 ++ FFUDevelopment/Apps/AppsList.txt | 2 - FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 18 +-- FFUDevelopment/BuildFFUVM.ps1 | 121 +++++++++++------- 4 files changed, 98 insertions(+), 57 deletions(-) create mode 100644 FFUDevelopment/Apps/AppList.json delete mode 100644 FFUDevelopment/Apps/AppsList.txt diff --git a/FFUDevelopment/Apps/AppList.json b/FFUDevelopment/Apps/AppList.json new file mode 100644 index 0000000..ea7e1d7 --- /dev/null +++ b/FFUDevelopment/Apps/AppList.json @@ -0,0 +1,14 @@ +{ + "apps": [ + { + "name": "7-Zip", + "id": "7zip.7zip", + "source": "winget" + }, + { + "name": "Company Portal", + "id": "9WZDNCRFJ3PZ", + "source": "msstore" + } + ] +} \ No newline at end of file diff --git a/FFUDevelopment/Apps/AppsList.txt b/FFUDevelopment/Apps/AppsList.txt deleted file mode 100644 index b4903ae..0000000 --- a/FFUDevelopment/Apps/AppsList.txt +++ /dev/null @@ -1,2 +0,0 @@ -winget:7-Zip -store:Company Portal \ No newline at end of file diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index fde1977..1fefc88 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -33,20 +33,20 @@ for /d %%D in ("%basepath%\*") do ( set "licensefile=%%F" ) if defined mainpackage ( + set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!"" if exist "!dependenciesfolder!" ( - set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!"" for %%G in ("!dependenciesfolder!\*") do ( set "dism_command=!dism_command! /DependencyPackagePath:"%%G"" ) - if defined licensefile ( - set "dism_command=!dism_command! /LicensePath:"!licensefile!"" - ) else ( - set "dism_command=!dism_command! /SkipLicense" - ) - set "dism_command=!dism_command! /Region:All" - echo !dism_command! - !dism_command! ) + if defined licensefile ( + set "dism_command=!dism_command! /LicensePath:"!licensefile!"" + ) else ( + set "dism_command=!dism_command! /SkipLicense" + ) + set "dism_command=!dism_command! /Region:All" + echo !dism_command! + !dism_command! ) ) :remaining diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index ef6da11..f0dadac 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1737,12 +1737,13 @@ function New-WinGetSettings { function Add-Win32SilentInstallCommand { param ( [string]$AppFolder, - [string]$AppFolderPath + [string]$AppFolderPath, + [string]$LineNumber ) $appName = $AppFolder - $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include *.exe, *.msi -File -ErrorAction Stop + $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop $installer = Split-Path -Path $installerPath -Leaf - $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include *.yaml -File -ErrorAction Stop + $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include "*.yaml" -File -ErrorAction Stop $yamlContent = Get-Content -Path $yamlFile -Raw $silentInstallSwitch = [regex]::Match($yamlContent, 'Silent:\s*(.+)').Groups[1].Value $silentInstallSwitch = $silentInstallSwitch.Replace("'", "").Trim() @@ -1758,6 +1759,11 @@ function Add-Win32SilentInstallCommand { elseif ($installerFileExtension -eq ".msi") { $silentInstallCommand = "msiexec /i `"D:\win32\$appFolder\$installer`" $silentInstallSwitch" } + else { + WriteLog "No win32 app installers were found. Skipping the inclusion of $appName" + Remove-Item -Path $AppFolderPath -Recurse -Force + return + } $cmdFile = "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent = Get-Content -Path $cmdFile $cmdContent = $cmdContent[0..($lineNumber - 2)] + $silentInstallCommand.Trim() + $cmdContent[($lineNumber - 1)..($cmdContent.Length - 1)] @@ -1767,38 +1773,59 @@ function Add-Win32SilentInstallCommand { function Get-WinGetApp { param ( - [string]$WinGetApp, + [string]$WinGetAppName, + [string]$WinGetAppId, [int]$LineNumber ) - $wingetSearchResult = & winget.exe search --name "$WinGetApp" --exact --accept-source-agreements --source winget + $wingetSearchResult = & winget.exe search --id "$WinGetAppId" --exact --accept-source-agreements --source winget if ($wingetSearchResult -contains "No package found matching input criteria.") { - WriteLog "$WinGetApp not found in WinGet repository. Skipping download." + WriteLog "$WinGetAppName not found in WinGet repository. Skipping download." return } - $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetApp + $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null $appFolder = Split-Path -Path $appFolderPath -Leaf - WriteLog "Downloading $WinGetApp..." - $wingetDownloadResult = & winget.exe download --name "$WinGetApp" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String + WriteLog "Downloading $WinGetAppName..." + $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String if ($wingetDownloadResult -match "No applicable installer found") { - $wingetDownloadResult = & winget.exe download --name "$WinGetApp" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String } if ($wingetDownloadResult -notmatch "Installer downloaded") { - WriteLog "$WinGetApp did not successfully download." + WriteLog "$WinGetAppName did not successfully download." Remove-Item -Path $appFolderPath -Recurse -Force return } - WriteLog "$WinGetApp has completed downloading." - Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath + WriteLog "$WinGetAppName has completed downloading to $appFolderPath" + $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop + $installer = Split-Path -Path $installerPath -Leaf + $installerFileExtension = [System.IO.Path]::GetExtension($installer) + $uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle") + if ($uwpExtensions -contains $installerFileExtension) { + New-Item -Path "$AppsPath\MSStore\$WinGetAppName" -ItemType Directory -Force | Out-Null + Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$WinGetAppName" -Force + Remove-Item -Path $appFolderPath -Force + $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { + WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." + $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent + } + # Since a Win32 app was not received, returning false to not increment line number for silent install command + return $false + } + else { + Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath -LineNumber $LineNumber + } } function Get-StoreApp { param ( - [string]$StoreApp + [string]$StoreAppName, + [string]$StoreAppId ) - $wingetSearchResult = & winget.exe search --name --exact "$StoreApp" --accept-source-agreements --source msstore + $wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore if ($wingetSearchResult -contains "No package found matching input criteria.") { - WriteLog "$StoreApp not found in WinGet repository. Skipping download." + WriteLog "$StoreAppName not found in WinGet repository. Skipping download." return } # Skip the header lines and get the line with the app information @@ -1806,22 +1833,22 @@ function Get-StoreApp { # Split the line by whitespace and get the second-to-last item (the Id) $appID = ($appResult -split '\s+')[-2] # Checking app ID to determine if store app is a win32 app - WriteLog "Checking if $StoreApp is a win32 app..." + WriteLog "Checking if $StoreAppName is a win32 app..." if ($appID.StartsWith("XP")) { - WriteLog "$StoreApp is a win32 app. Adding to $AppsPath\win32 folder" - $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreApp + WriteLog "$StoreAppName is a win32 app. Adding to $AppsPath\win32 folder" + $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null $appFolder = Split-Path -Path $appFolderPath -Leaf - WriteLog "Downloading $StoreApp for $WindowsArch architecture..." - $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String + WriteLog "Downloading $StoreAppName for $WindowsArch architecture..." + $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String if ($wingetDownloadResult -match "No applicable installer found") { WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." - $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match $StoreApp){ - WriteLog "Downloaded $StoreApp without specifying architecture." + $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + if ($wingetDownloadResult -match $StoreAppName){ + WriteLog "Downloaded $StoreAppName without specifying architecture." } else { - WriteLog "No installer found for $StoreApp. Skipping download." + WriteLog "No installer found for $StoreAppName. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force return } @@ -1829,19 +1856,19 @@ function Get-StoreApp { Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath return } - $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreApp + $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. # WinGet's download command will return a non-zero exit code when downloading store apps, as attempting to download the license file always appears to cause an error. - WriteLog "Downloading $StoreApp and dependencies..." + WriteLog "Downloading $StoreAppName and dependencies..." 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.' - $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String + $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | 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..." - $wingetDownloadResult = & winget.exe download --name --exact "$StoreApp" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match $StoreApp){ - WriteLog "Downloaded $StoreApp without specifying architecture." + $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String + if ($wingetDownloadResult -match $StoreAppName){ + WriteLog "Downloaded $StoreAppName without specifying architecture." # 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.' @@ -1857,14 +1884,14 @@ function Get-StoreApp { } } else { - WriteLog "No installer found for $StoreApp. Skipping download." + WriteLog "No installer found for $StoreAppName. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force return } } # Many store apps can be found by winget search, but the download of the apps are unsupported. if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { - WriteLog "No applicable Microsoft Store package download information found for $StoreApp. Skipping download." + WriteLog "No applicable Microsoft Store package download information found for $StoreAppName. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force return } @@ -1874,7 +1901,7 @@ function Get-StoreApp { $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent } - WriteLog "$StoreApp has completed downloading. Identifying the latest version of $StoreApp." + 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 = "" @@ -1890,7 +1917,7 @@ function Get-StoreApp { } } # Removing all packages that are not the latest version - WriteLog "Latest version of $StoreApp has been identified as $latestPackage. Removing old versions of $StoreApp that may have downloaded." + 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 { @@ -1909,19 +1936,19 @@ function Get-Apps { param ( [string]$AppsList ) - $apps = Get-Content -Path $AppsList + $apps = Get-Content -Path $AppsList -Raw | ConvertFrom-Json if (-not $apps) { WriteLog "No apps were specified in AppsList.txt file." return } $wingetApps = @() $storeApps = @() - $apps | ForEach-Object { - if ($_ -like 'winget:*') { - $wingetApps += $_.Substring(7).Trim() - } - elseif ($_ -like 'store:*') { - $storeApps += $_.Substring(6).Trim() + foreach ($app in $apps.apps) { + if ($app.source -eq "winget") { + $wingetApps += $app + } + elseif ($app.source -eq "msstore") { + $storeApps += $app } } $wingetInstalled = Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" -ErrorAction SilentlyContinue @@ -1949,8 +1976,10 @@ function Get-Apps { } foreach ($wingetApp in $wingetApps) { try { - Get-WinGetApp -WinGetApp $wingetApp -LineNumber $lineNumber - $lineNumber++ + $result = Get-WinGetApp -WinGetAppName $wingetApp.Name -WinGetAppId $wingetApp.Id -LineNumber $lineNumber + if ($null -eq $result) { + $lineNumber++ + } } catch { WriteLog "Error occurred while processing $wingetApp : $_" @@ -1965,7 +1994,7 @@ function Get-Apps { } foreach ($storeApp in $storeApps) { try { - Get-StoreApp -StoreApp $storeApp + Get-StoreApp -StoreAppName $storeApps.Name -StoreAppId $storeApps.Id } catch { WriteLog "Error occurred while processing $storeApp : $_" @@ -3383,7 +3412,7 @@ if ($InstallApps) { exit } WriteLog "$AppsPath\InstallAppsandSysprep.cmd found" - Get-Apps -AppsList "$AppsPath\AppsList.txt" + Get-Apps -AppsList "$AppsPath\AppList.json" if (-not $InstallOffice) { #Modify InstallAppsandSysprep.cmd to REM out the office install command $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" From 146c1601bd47474b2d9997d8603f2a98ea5f764d Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 5 Jul 2024 00:01:06 -0400 Subject: [PATCH 18/43] Improved handling of store apps that can't be downloaded --- FFUDevelopment/BuildFFUVM.ps1 | 38 ++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index f0dadac..d176963 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1738,7 +1738,7 @@ function Add-Win32SilentInstallCommand { param ( [string]$AppFolder, [string]$AppFolderPath, - [string]$LineNumber + [int]$LineNumber ) $appName = $AppFolder $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop @@ -1821,7 +1821,8 @@ function Get-WinGetApp { function Get-StoreApp { param ( [string]$StoreAppName, - [string]$StoreAppId + [string]$StoreAppId, + [int]$LineNumber ) $wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore if ($wingetSearchResult -contains "No package found matching input criteria.") { @@ -1853,21 +1854,33 @@ function Get-StoreApp { return } } - Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath - return + Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath -LineNumber $LineNumber + # Since a Win32 app was received, returning false to increment line number for silent install command + return $false } $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. # WinGet's download command will return a non-zero exit code when downloading store apps, as attempting to download the license file always appears to cause an error. - WriteLog "Downloading $StoreAppName and dependencies..." + WriteLog "Attempting to download $StoreAppName and dependencies..." 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.' $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String + if ($wingetDownloadResult -match "The request is not supported") { + WriteLog "The download request is not supported for $StoreAppName. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } + # Many store apps can be found by winget search, but the download of the apps are unsupported. + if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { + WriteLog "No applicable Microsoft Store package download information found for $StoreAppName. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return + } # 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..." $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match $StoreAppName){ + if ($wingetDownloadResult -match "Installer downloaded") { WriteLog "Downloaded $StoreAppName without specifying architecture." # If $WindowsArch -eq 'ARM64', remove all dependency files that are not ARM64 if ($WindowsArch -eq 'ARM64') { @@ -1884,17 +1897,11 @@ function Get-StoreApp { } } else { - WriteLog "No installer found for $StoreAppName. Skipping download." + WriteLog "No installer found for $StoreAppName from the msstore source. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force return } } - # Many store apps can be found by winget search, but the download of the apps are unsupported. - if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { - WriteLog "No applicable Microsoft Store package download information found for $StoreAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." @@ -1994,7 +2001,10 @@ function Get-Apps { } foreach ($storeApp in $storeApps) { try { - Get-StoreApp -StoreAppName $storeApps.Name -StoreAppId $storeApps.Id + $result = Get-StoreApp -StoreAppName $storeApp.Name -StoreAppId $storeApp.Id -LineNumber $lineNumber + if ($result -eq $false) { + $lineNumber++ + } } catch { WriteLog "Error occurred while processing $storeApp : $_" From 174c16ecb67b86cc3751273959726bf07351f225 Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:48:39 -0400 Subject: [PATCH 19/43] Add files via upload Upload BuildUSBDrives.ps1 --- FFUDevelopment/BuildUSBDrives.ps1 | 245 ++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 FFUDevelopment/BuildUSBDrives.ps1 diff --git a/FFUDevelopment/BuildUSBDrives.ps1 b/FFUDevelopment/BuildUSBDrives.ps1 new file mode 100644 index 0000000..a797701 --- /dev/null +++ b/FFUDevelopment/BuildUSBDrives.ps1 @@ -0,0 +1,245 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $True, Position = 0)] + $DeployISOPath, + [Switch]$DisableAutoPlay +) +$Host.UI.RawUI.WindowTitle = 'Imaging Tool USB Creator' + +if($DeployISOPath){ +$DevelopmentPath = $DeployISOPath | Split-Path +$ImagesPath = "$DevelopmentPath\FFU" +function WriteLog($LogText) { +$LogFileName = '\Script.log' +$LogFile = $DevelopmentPath + $LogFilename + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue + Write-Verbose $LogText +} + +function Write-ProgressLog { + param( + [string]$Activity, + [string]$Status + ) + Write-Progress -Activity $Activity -Status $Status -PercentComplete (($currentStep / $totalSteps) * 100) + WriteLog $Status + $script:currentStep++ + + } +Function Get-RemovableDrive { +writelog "Get information for all removable drives" +$USBDrives = Get-WmiObject Win32_DiskDrive | Where-Object {$_.MediaType -eq "Removable media"} +If($USBDrives -and ($null -eq $USBDrives.count)) { + $USBDrivesCount = 1 + } + else { + $USBDrivesCount = $USBDrives.Count + } + WriteLog "Found $USBDrivesCount USB drives" + + if ($null -eq $USBDrives) { + WriteLog "No removable USB drive found. Exiting" + Write-Error "No removable USB drive found. Exiting" + Pause + exit 1 + } + return $USBDrives, $USBDrivesCount + } + +Function Build-DeploymentUSB{ + param( + [Array]$Drives + ) + writelog "Creating list of FFU image files" + $Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse + writelog "Checking if drivers are present in the drivers folder" + $Drivers = Get-ChildItem -Path $DriversPath -Recurse + $DrivesCount = $Drives.Count + Write-ProgressLog "Create Imaging Tool" "Creating partitions..." + writelog "Create job to partition each usb drive" + foreach ($USBDrive in $Drives) { + $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") + $Model = $USBDrive.model + $ScriptBlock = { + param($DriveNumber) + Clear-Disk -Number $DriveNumber -RemoveData -RemoveOEM -Confirm:$false + $Disk = Get-Disk -Number $DriveNumber + $PartitionStyle = $Disk.PartitionStyle + if($PartitionStyle -ne 'MBR'){ + $Disk | Set-Disk -PartitionStyle MBR + } + $BootPartition = New-Partition -DiskNumber $DriveNumber -Size 2GB -IsActive -AssignDriveLetter + $DeployPartition = New-Partition -DiskNumber $DriveNumber -UseMaximumSize -AssignDriveLetter + Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "Boot" -Confirm:$false + Format-Volume -Partition $DeployPartition -FileSystem NTFS -NewFileSystemLabel "Deploy" -Confirm:$false + } + WriteLog "Start job to create BOOT and Deploy partitions on drive number $DriveNumber" + Start-Job -ScriptBlock $ScriptBlock -ArgumentList $DriveNumber | Out-Null + } + writelog "Wait for partitioning jobs to complete" + Get-Job | Wait-Job | Out-Null + if($DrivesCount -gt 1){ + writelog "Get file system information for all drives" + $Partitions = Get-Partition | Get-Volume + }else{ + writelog "Get file system information for drive number $DiskNumber" + $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume + } +writelog "Get drive letter for all volumes labeled:BOOT" +$BootDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "BOOT"}).DriveLetter +writelog "Get drive letter for all volumes labeled:Deploy" +$DeployDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "Deploy"}).DriveLetter +writelog "Mount Deployment .iso image" +$ISOMountPoint = (Mount-DiskImage -ImagePath "$DeployISOPath" -PassThru | Get-Volume).DriveLetter + ":\" +writelog "Copying boot files to all drives labeled BOOT concurrently" +foreach ($Drive in $BootDrives) { +$Destination = $Drive + ":\" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } + WriteLog "Start job to copy all boot files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ISOMountPoint, $Destination | Out-Null +} +if($Images){ +writelog "Copying FFU image files to all drives labeled deploy concurrently" +foreach ($Drive in $DeployDrives) { +$Destination = $Drive + ":\Images" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } + + WriteLog "Start job to copy all FFU files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ImagesPath, $Destination | Out-Null + } +} +if(!($Images)){ + foreach ($Drive in $DeployDrives) { + WriteLog "Create images directory" + $drivepath = $Drive + ":\" + New-Item -Path "$drivepath" -Name Images -ItemType Directory -Force -Confirm: $false | Out-Null + } +} +if($Drivers){ +writelog "Copying driver files to all drives labeled deploy concurrently" +foreach ($Drive in $DeployDrives) { +$Destination = $Drive + ":\Drivers" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } + WriteLog "Start job to copy all drivers to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $DriversPath, $Destination | Out-Null +} +} +if(!($Drivers)){ + foreach ($Drive in $DeployDrives) { + WriteLog "Create drivers directory" + $drivepath = $Drive + ":\" + New-Item -Path "$drivepath" -Name Drivers -ItemType Directory -Force -Confirm: $false | Out-Null + } +} +if($DrivesCount -gt 1){ +Write-ProgressLog "Create Imaging Tool" "Building $DrivesCount drives concurrently...Please be patient..." +}else{ +Write-ProgressLog "Create Imaging Tool" "Building the imaging tool on $model...Please be patient..." +} +Get-Job | Wait-Job | Out-Null + +Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null +Write-ProgressLog "Create Imaging Tool" "Drive creation jobs completed..." +} + +Function New-DeploymentUSB { + param( + [Array]$Drives, + [int]$Count, + [String]$FFUPath = "$DevelopmentPath\FFU", + [String]$DriversPath = "$DevelopmentPath\Drivers" + + ) + + $Drivelist = @() + writelog "Creating a USB drive selection list" + for($i=0;$i -le $Count -1;$i++){ + $DriveModel = $Drives[$i].Model + $DriveSize = [math]::round($Drives[$i].size/1GB, 2) + $DiskNumber = $Drives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "") + $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize} + + $Drivelist += New-Object PSObject -Property $Properties + } + if($Count -gt 1){ + $Last = $Count+1 + $Drivelist += New-Object -TypeName PSObject -Property @{ Number = "$last"; DriveModel = "Select this option to use all ($count) inserted USB Drives" } + } + $Drivelist | Format-Table -AutoSize -Property Number, DriveModel , 'Size (GB)' + do { + try { + $var = $true + $DriveSelected = Read-Host 'Enter the drive number to apply the .iso to' + $DriveSelected = ($DriveSelected -as [int]) -1 + writelog "Drive $DriveSelected selected" + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($DriveSelected -le $Count -1 -or $last) -and $var) + if($DisableAutoPlay){ + WriteLog "Setting the registry key to disable autoplay for all drives" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD + } + WriteLog "Closing all MMC windows to prevent drive lock errors" + Stop-Process -Name mmc -ErrorAction SilentlyContinue + WriteLog "Closing all Diskpart windows to prevent drive lock errors" + Stop-Process -Name diskpart -ErrorAction SilentlyContinue + $Selection = $Drivelist[$DriveSelected].Number + $totalSteps = 5 + if($Selection -eq $last){ + Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives + }else{ + Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives[$DriveSelected] + } + WriteLog "Setting the registry key to re-enable autoplay for all drives" + if($DisableAutoPlay){ + Write-ProgressLog "Create Imaging Tool" "Enabling Autoplay" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 0 -Type DWORD + } + Write-ProgressLog "Create Imaging Tool" "Completed!" +} +#Get USB Drive and create log file +if(Test-Path "$DevelopmentPath\Script.log"){ +Remove-Item -Path "$DevelopmentPath\Script.log" -Force -Confirm:$false +New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out-Null +} +WriteLog 'Begin Logging' +WriteLog 'Getting USB drive information and usb drive count' +$USBDrives,$USBDrivesCount = Get-RemovableDrive +WriteLog 'Setting first step for percentage progress bar' +$currentStep = 1 +New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount + +read-host -Prompt "USB drive creation complete. Press ENTER to exit" + +Exit +}else{ +Write-Host "No .ISO file selected..." +read-host "Press ENTER to Exit..." +Exit +} From 325413de13a09bf1f4a4acbf62744e3b806e5798 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:27:27 -0400 Subject: [PATCH 20/43] Moved code into separate functions, refactored existing functions, fixed logical errors in if-statements --- FFUDevelopment/BuildFFUVM.ps1 | 142 ++++++++++++++-------------------- 1 file changed, 57 insertions(+), 85 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index d176963..0b88fbc 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1695,6 +1695,22 @@ function Install-WinGet { } } +function Confirm-WinGetInstallation { + $wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" + if (-not (Test-Path $wingetPath)) { + WriteLog "WinGet is not installed. Downloading preview version of WinGet and its dependencies..." + Install-WinGet -InstallWithDependencies $true + } + elseif (-not (Get-Command winget -ErrorAction SilentlyContinue)) { + WriteLog "WinGet is not on the path. Downloading preview version of WinGet without dependencies..." + Install-WinGet -InstallWithDependencies $false + } + elseif (-not ((& winget.exe --version) -like "*preview*")) { + WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet without dependencies..." + Install-WinGet -InstallWithDependencies $false + } +} + function New-WinGetSettings { $wingetSettingsFile = "$env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json" $wingetSettings = @( @@ -1742,28 +1758,26 @@ function Add-Win32SilentInstallCommand { ) $appName = $AppFolder $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop - $installer = Split-Path -Path $installerPath -Leaf + if (-not $installerPath) { + WriteLog "No win32 app installers were found. Skipping the inclusion of $AppFolder" + Remove-Item -Path $AppFolderPath -Recurse -Force + return $false + } $yamlFile = Get-ChildItem -Path "$appFolderPath\*" -Include "*.yaml" -File -ErrorAction Stop $yamlContent = Get-Content -Path $yamlFile -Raw - $silentInstallSwitch = [regex]::Match($yamlContent, 'Silent:\s*(.+)').Groups[1].Value - $silentInstallSwitch = $silentInstallSwitch.Replace("'", "").Trim() + $silentInstallSwitch = [regex]::Match($yamlContent, 'Silent:\s*(.+)').Groups[1].Value.Replace("'", "").Trim() if (-not $silentInstallSwitch) { WriteLog "Silent install switch for $appName could not be found. Skipping the inclusion of $appName." Remove-Item -Path $appFolderPath -Recurse -Force - return + return $false } - $installerFileExtension = [System.IO.Path]::GetExtension($installer) - if ($installerFileExtension -eq ".exe") { + $installer = Split-Path -Path $installerPath -Leaf + if ($installerPath.Extension -eq ".exe") { $silentInstallCommand = "`"D:\win32\$appFolder\$installer`" $silentInstallSwitch" } - elseif ($installerFileExtension -eq ".msi") { + elseif ($installerPath.Extension -eq ".msi") { $silentInstallCommand = "msiexec /i `"D:\win32\$appFolder\$installer`" $silentInstallSwitch" } - else { - WriteLog "No win32 app installers were found. Skipping the inclusion of $appName" - Remove-Item -Path $AppFolderPath -Recurse -Force - return - } $cmdFile = "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent = Get-Content -Path $cmdFile $cmdContent = $cmdContent[0..($lineNumber - 2)] + $silentInstallCommand.Trim() + $cmdContent[($lineNumber - 1)..($cmdContent.Length - 1)] @@ -1771,6 +1785,16 @@ function Add-Win32SilentInstallCommand { Set-Content -Path $cmdFile -Value $cmdContent } +function Set-InstallStoreAppsFlag { + $cmdPath = "$AppsPath\InstallAppsandSysprep.cmd" + $cmdContent = Get-Content -Path $cmdPath + if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { + WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." + $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent + } +} + function Get-WinGetApp { param ( [string]$WinGetAppName, @@ -1780,11 +1804,11 @@ function Get-WinGetApp { $wingetSearchResult = & winget.exe search --id "$WinGetAppId" --exact --accept-source-agreements --source winget if ($wingetSearchResult -contains "No package found matching input criteria.") { WriteLog "$WinGetAppName not found in WinGet repository. Skipping download." - return + # Return false to not increment line number for silent install command. + return $false } $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - $appFolder = Split-Path -Path $appFolderPath -Leaf WriteLog "Downloading $WinGetAppName..." $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String if ($wingetDownloadResult -match "No applicable installer found") { @@ -1793,28 +1817,20 @@ function Get-WinGetApp { if ($wingetDownloadResult -notmatch "Installer downloaded") { WriteLog "$WinGetAppName did not successfully download." Remove-Item -Path $appFolderPath -Recurse -Force - return + return $false } WriteLog "$WinGetAppName has completed downloading to $appFolderPath" $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop - $installer = Split-Path -Path $installerPath -Leaf - $installerFileExtension = [System.IO.Path]::GetExtension($installer) $uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle") - if ($uwpExtensions -contains $installerFileExtension) { + if ($uwpExtensions -contains $installerPath.Extension) { New-Item -Path "$AppsPath\MSStore\$WinGetAppName" -ItemType Directory -Force | Out-Null Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$WinGetAppName" -Force Remove-Item -Path $appFolderPath -Force - $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" - if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { - WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." - $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' - Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent - } - # Since a Win32 app was not received, returning false to not increment line number for silent install command + Set-InstallStoreAppsFlag return $false } else { - Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath -LineNumber $LineNumber + Add-Win32SilentInstallCommand -AppFolder $WinGetAppName -AppFolderPath $appFolderPath -LineNumber $LineNumber } } @@ -1839,13 +1855,12 @@ function Get-StoreApp { WriteLog "$StoreAppName is a win32 app. Adding to $AppsPath\win32 folder" $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - $appFolder = Split-Path -Path $appFolderPath -Leaf WriteLog "Downloading $StoreAppName for $WindowsArch architecture..." $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String if ($wingetDownloadResult -match "No applicable installer found") { WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match $StoreAppName){ + if ($wingetDownloadResult -match "Installer downloaded"){ WriteLog "Downloaded $StoreAppName without specifying architecture." } else { @@ -1854,7 +1869,7 @@ function Get-StoreApp { return } } - Add-Win32SilentInstallCommand -AppFolder $appFolder -AppFolderPath $appFolderPath -LineNumber $LineNumber + Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath -LineNumber $LineNumber # Since a Win32 app was received, returning false to increment line number for silent install command return $false } @@ -1865,22 +1880,11 @@ function Get-StoreApp { WriteLog "Attempting to download $StoreAppName and dependencies..." 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.' $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String - if ($wingetDownloadResult -match "The request is not supported") { - WriteLog "The download request is not supported for $StoreAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } - # Many store apps can be found by winget search, but the download of the apps are unsupported. - if ($wingetDownloadResult -match "No applicable Microsoft Store package download information found.") { - WriteLog "No applicable Microsoft Store package download information found for $StoreAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } # 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..." $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match "Installer downloaded") { + if ($wingetDownloadResult -match "Microsoft Store package download completed") { WriteLog "Downloaded $StoreAppName without specifying architecture." # If $WindowsArch -eq 'ARM64', remove all dependency files that are not ARM64 if ($WindowsArch -eq 'ARM64') { @@ -1902,27 +1906,16 @@ function Get-StoreApp { return } } - $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" - if ($cmdContent -match 'set "INSTALL_STOREAPPS=false"') { - WriteLog "Setting INSTALL_STOREAPPS flag to true in InstallAppsandSysprep.cmd file." - $updatedcmdContent = $cmdContent -replace 'set "INSTALL_STOREAPPS=false"', 'set "INSTALL_STOREAPPS=true"' - Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $updatedcmdContent + elseif ($wingetDownloadResult -notmatch "Microsoft Store package download completed") { + WriteLog "Download not supported for $StoreAppName. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return } + Set-InstallStoreAppsFlag 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 = "" - $latestDate = [datetime]::MinValue - foreach ($package in $packages) { - $signature = Get-AuthenticodeSignature -FilePath $package.FullName - if ($signature.Status -eq 'Valid') { - $signatureDate = $signature.SignerCertificate.NotBefore - if ($signatureDate -gt $latestDate) { - $latestPackage = $package.FullName - $latestDate = $signatureDate - } - } - } + $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) { @@ -1945,35 +1938,12 @@ function Get-Apps { ) $apps = Get-Content -Path $AppsList -Raw | ConvertFrom-Json if (-not $apps) { - WriteLog "No apps were specified in AppsList.txt file." + WriteLog "No apps were specified in AppList.json file." return } - $wingetApps = @() - $storeApps = @() - foreach ($app in $apps.apps) { - if ($app.source -eq "winget") { - $wingetApps += $app - } - elseif ($app.source -eq "msstore") { - $storeApps += $app - } - } - $wingetInstalled = Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" -ErrorAction SilentlyContinue - if (-not $wingetInstalled) { - WriteLog "WinGet is not installed. Downloading preview version of WinGet and its dependencies..." - Install-WinGet -InstallWithDependencies $true - } - $wingetOnPath = Get-Command winget -ErrorAction SilentlyContinue - if (-not $wingetOnPath) { - WriteLog "WinGet is not on the path. Downloading preview version of WinGet without dependencies..." - Install-WinGet -InstallWithDependencies $false - } - $wingetVersion = & winget.exe --version - # Preview release is needed to enable storeDownload experimental feature - if (-not ($wingetVersion -like "*preview*")) { - WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet without dependencies..." - Install-WinGet -InstallWithDependencies $false - } + $wingetApps = $apps.apps | Where-Object { $_.source -eq "winget" } + $storeApps = $apps.apps | Where-Object { $_.source -eq "msstore" } + Confirm-WinGetInstallation $lineNumber = 13 $win32Folder = Join-Path -Path $AppsPath -ChildPath "Win32" $storeAppsFolder = Join-Path -Path $AppsPath -ChildPath "MSStore" @@ -1986,6 +1956,7 @@ function Get-Apps { $result = Get-WinGetApp -WinGetAppName $wingetApp.Name -WinGetAppId $wingetApp.Id -LineNumber $lineNumber if ($null -eq $result) { $lineNumber++ + WriteLog "Line number incremented to $lineNumber" } } catch { @@ -2004,6 +1975,7 @@ function Get-Apps { $result = Get-StoreApp -StoreAppName $storeApp.Name -StoreAppId $storeApp.Id -LineNumber $lineNumber if ($result -eq $false) { $lineNumber++ + WriteLog "Line number incremented to $lineNumber" } } catch { From 3f0377fbf99481af440a16c49c75f5f9a31ff826 Mon Sep 17 00:00:00 2001 From: Matthew Haley <30245+mhaley@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:45:44 -0700 Subject: [PATCH 21/43] Assign drive letter to recovery partition before modification Assign drive letter so copy succeeds, remove when finished. --- FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 index c6b9491..4c9c69d 100644 --- a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -495,10 +495,12 @@ $WinRE = $USBDrive + "WinRE\winre.wim" If (Test-Path -Path $WinRE) { WriteLog 'Copying modified WinRE to Recovery directory' + Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object Type -eq Recovery | Set-Partition -NewDriveLetter R Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" WriteLog 'Copying WinRE to Recovery directory succeeded' WriteLog 'Registering location of recovery tools' Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object Type -eq Recovery | Remove-PartitionAccessPath -AccessPath R: WriteLog 'Registering location of recovery tools succeeded' } # else From 67cc8c122546d7f45b7c41f2d242ea1fdb167d9c Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:29:58 -0400 Subject: [PATCH 22/43] Update BuildUSBDrives.ps1 Update code --- FFUDevelopment/BuildUSBDrives.ps1 | 67 ++++++++++--------------------- 1 file changed, 21 insertions(+), 46 deletions(-) diff --git a/FFUDevelopment/BuildUSBDrives.ps1 b/FFUDevelopment/BuildUSBDrives.ps1 index a797701..514acce 100644 --- a/FFUDevelopment/BuildUSBDrives.ps1 +++ b/FFUDevelopment/BuildUSBDrives.ps1 @@ -8,28 +8,15 @@ $Host.UI.RawUI.WindowTitle = 'Imaging Tool USB Creator' if($DeployISOPath){ $DevelopmentPath = $DeployISOPath | Split-Path -$ImagesPath = "$DevelopmentPath\FFU" function WriteLog($LogText) { $LogFileName = '\Script.log' $LogFile = $DevelopmentPath + $LogFilename Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue Write-Verbose $LogText } - -function Write-ProgressLog { - param( - [string]$Activity, - [string]$Status - ) - Write-Progress -Activity $Activity -Status $Status -PercentComplete (($currentStep / $totalSteps) * 100) - WriteLog $Status - $script:currentStep++ - - } -Function Get-RemovableDrive { -writelog "Get information for all removable drives" -$USBDrives = Get-WmiObject Win32_DiskDrive | Where-Object {$_.MediaType -eq "Removable media"} -If($USBDrives -and ($null -eq $USBDrives.count)) { +Function Get-USBDrive { + $USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'") + If ($USBDrives -and ($null -eq $USBDrives.count)) { $USBDrivesCount = 1 } else { @@ -40,23 +27,20 @@ If($USBDrives -and ($null -eq $USBDrives.count)) { if ($null -eq $USBDrives) { WriteLog "No removable USB drive found. Exiting" Write-Error "No removable USB drive found. Exiting" - Pause exit 1 } - return $USBDrives, $USBDrivesCount - } - + return $USBDrives, $USBDrivesCount +} Function Build-DeploymentUSB{ param( [Array]$Drives ) - writelog "Creating list of FFU image files" + writelog "Checking if ffu files are present in the ffu folder" $Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse writelog "Checking if drivers are present in the drivers folder" $Drivers = Get-ChildItem -Path $DriversPath -Recurse $DrivesCount = $Drives.Count - Write-ProgressLog "Create Imaging Tool" "Creating partitions..." - writelog "Create job to partition each usb drive" + Writelog "Creating partitions..." foreach ($USBDrive in $Drives) { $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") $Model = $USBDrive.model @@ -107,27 +91,19 @@ $Destination = $Drive + ":\" if($Images){ writelog "Copying FFU image files to all drives labeled deploy concurrently" foreach ($Drive in $DeployDrives) { -$Destination = $Drive + ":\Images" +$Destination = $Drive + ":\" $jobScriptBlock = { param ( [string]$SFolder, [string]$DFolder ) - New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J } WriteLog "Start job to copy all FFU files to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ImagesPath, $Destination | Out-Null + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $FFUPath, $Destination | Out-Null } } -if(!($Images)){ - foreach ($Drive in $DeployDrives) { - WriteLog "Create images directory" - $drivepath = $Drive + ":\" - New-Item -Path "$drivepath" -Name Images -ItemType Directory -Force -Confirm: $false | Out-Null - } -} if($Drivers){ writelog "Copying driver files to all drives labeled deploy concurrently" foreach ($Drive in $DeployDrives) { @@ -152,14 +128,14 @@ if(!($Drivers)){ } } if($DrivesCount -gt 1){ -Write-ProgressLog "Create Imaging Tool" "Building $DrivesCount drives concurrently...Please be patient..." +Writelog "Building $DrivesCount drives concurrently...Please be patient..." }else{ -Write-ProgressLog "Create Imaging Tool" "Building the imaging tool on $model...Please be patient..." +Writelog "Building the imaging tool on $model...Please be patient..." } Get-Job | Wait-Job | Out-Null Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null -Write-ProgressLog "Create Imaging Tool" "Drive creation jobs completed..." +Writelog "Drive creation jobs completed..." } Function New-DeploymentUSB { @@ -178,7 +154,7 @@ Function New-DeploymentUSB { $DriveSize = [math]::round($Drives[$i].size/1GB, 2) $DiskNumber = $Drives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "") $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize} - + $Drivelist += New-Object PSObject -Property $Properties } if($Count -gt 1){ @@ -191,10 +167,12 @@ Function New-DeploymentUSB { $var = $true $DriveSelected = Read-Host 'Enter the drive number to apply the .iso to' $DriveSelected = ($DriveSelected -as [int]) -1 - writelog "Drive $DriveSelected selected" + if($Last){ + writelog "All drives selected" + }else{ + writelog "Drive $DriveSelected selected"} } - - catch { + catch { Write-Host 'Input was not in correct format. Please enter a valid FFU number' $var = $false } @@ -218,10 +196,10 @@ Function New-DeploymentUSB { } WriteLog "Setting the registry key to re-enable autoplay for all drives" if($DisableAutoPlay){ - Write-ProgressLog "Create Imaging Tool" "Enabling Autoplay" + Writelog "Enabling Autoplay" Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 0 -Type DWORD } - Write-ProgressLog "Create Imaging Tool" "Completed!" + Writelog "Completed!" } #Get USB Drive and create log file if(Test-Path "$DevelopmentPath\Script.log"){ @@ -230,11 +208,8 @@ New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out } WriteLog 'Begin Logging' WriteLog 'Getting USB drive information and usb drive count' -$USBDrives,$USBDrivesCount = Get-RemovableDrive -WriteLog 'Setting first step for percentage progress bar' -$currentStep = 1 +$USBDrives,$USBDrivesCount = Get-USBDrive New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount - read-host -Prompt "USB drive creation complete. Press ENTER to exit" Exit From 6d85e3ef624469fb556b9192714fb7c281eea9ab Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:38:33 -0400 Subject: [PATCH 23/43] Rename BuildUSBDrives.ps1 to USBImagingToolCreator.ps1 Update Script name --- FFUDevelopment/{BuildUSBDrives.ps1 => USBImagingToolCreator.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FFUDevelopment/{BuildUSBDrives.ps1 => USBImagingToolCreator.ps1} (100%) diff --git a/FFUDevelopment/BuildUSBDrives.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 similarity index 100% rename from FFUDevelopment/BuildUSBDrives.ps1 rename to FFUDevelopment/USBImagingToolCreator.ps1 From ab0b7f67eca7bb9904bd05ff8a2cc20894de350a Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:40:22 -0400 Subject: [PATCH 24/43] Update USBImagingToolCreator.ps1 --- FFUDevelopment/USBImagingToolCreator.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/USBImagingToolCreator.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 index 514acce..622e30c 100644 --- a/FFUDevelopment/USBImagingToolCreator.ps1 +++ b/FFUDevelopment/USBImagingToolCreator.ps1 @@ -4,7 +4,7 @@ param( $DeployISOPath, [Switch]$DisableAutoPlay ) -$Host.UI.RawUI.WindowTitle = 'Imaging Tool USB Creator' +$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator' if($DeployISOPath){ $DevelopmentPath = $DeployISOPath | Split-Path From c0bcfd8bf2f14a9bdc467a39034265579e06ec3f Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:52:09 -0400 Subject: [PATCH 25/43] Update USBImagingToolCreator.ps1 Changed the way the DisableAutoplay switch runs. Now it only changes the registry setting if it the current value is set to "0". then it restores the setting back to the previous value --- FFUDevelopment/USBImagingToolCreator.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/FFUDevelopment/USBImagingToolCreator.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 index 622e30c..dd3f36d 100644 --- a/FFUDevelopment/USBImagingToolCreator.ps1 +++ b/FFUDevelopment/USBImagingToolCreator.ps1 @@ -177,7 +177,10 @@ Function New-DeploymentUSB { $var = $false } } until (($DriveSelected -le $Count -1 -or $last) -and $var) - if($DisableAutoPlay){ + + $DisableAutoPlayCurrentSetting = (Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name DisableAutoplay).DisableAutoplay + if($DisableAutoPlay -and $DisableAutoPlayCurrentSetting -ne 1){ + writelog "Disable autoPlay current setting is $DisableAutoPlayCurrentSetting" WriteLog "Setting the registry key to disable autoplay for all drives" Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD } @@ -196,8 +199,8 @@ Function New-DeploymentUSB { } WriteLog "Setting the registry key to re-enable autoplay for all drives" if($DisableAutoPlay){ - Writelog "Enabling Autoplay" - Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 0 -Type DWORD + Writelog "Setting disable autoplay setting back to $DisableAutoPlayCurrentSetting" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value $DisableAutoPlayCurrentSetting -Type DWORD } Writelog "Completed!" } From a9afba918557c816ee00088e5f108b77c25dce5a Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Thu, 11 Jul 2024 23:32:58 -0400 Subject: [PATCH 26/43] Refactor Install-WinGet and New-WinGetSettings for improved readability --- FFUDevelopment/BuildFFUVM.ps1 | 103 +++++++++++++++++----------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 0b88fbc..d64bcbc 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1659,55 +1659,48 @@ function Get-Office { } function Install-WinGet { - param ( - [bool]$InstallWithDependencies - ) $wingetPreviewLink = "https://aka.ms/getwingetpreview" $wingetPackageDestination = "$env:TEMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" - if ($InstallWithDependencies) { - $dependencies = @( - @{ - Source = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx" - Destination = "$env:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx" - }, - @{ - Source = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx" - Destination = "$env:TEMP\Microsoft.UI.Xaml.2.8.x64.appx" - } - ) - Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination - foreach ($dependency in $dependencies) { - Start-BitsTransferWithRetry -Source $dependency.Source -Destination $dependency.Destination - Add-AppxPackage -Path $dependency.Destination - Remove-Item -Path $dependency.Destination -Force -ErrorAction SilentlyContinue + $dependencies = @( + @{ + Source = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx" + Destination = "$env:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx" + }, + @{ + Source = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx" + Destination = "$env:TEMP\Microsoft.UI.Xaml.2.8.x64.appx" } - Add-AppxPackage -Path $wingetPackageDestination - Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue - } - else { - # If WinGet was already installed, then installing the dependencies can cause an error if the system has a newer version of the dependencies than the ones downloaded. - WriteLog "Downloading WinGet..." - Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination - WriteLog "Installing WinGet..." - Add-AppxPackage -Path $wingetPackageDestination - WriteLog "Removing WinGet installer..." - Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue + ) + foreach ($dependency in $dependencies) { + $dependencyName = [System.IO.Path]::GetFileName($dependency.Source) + WriteLog "Downloading $dependencyName..." + Start-BitsTransferWithRetry -Source $dependency.Source -Destination $dependency.Destination + WriteLog "Installing $dependencyName..." + Add-AppxPackage -Path $dependency.Destination -ErrorAction SilentlyContinue + WriteLog "Removing $dependencyName..." + Remove-Item -Path $dependency.Destination -Force -ErrorAction SilentlyContinue } + WriteLog "Downloading WinGet..." + Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination + WriteLog "Installing WinGet..." + Add-AppxPackage -Path $wingetPackageDestination -ErrorAction SilentlyContinue + WriteLog "Removing WinGet installer..." + Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue } function Confirm-WinGetInstallation { $wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" if (-not (Test-Path $wingetPath)) { - WriteLog "WinGet is not installed. Downloading preview version of WinGet and its dependencies..." - Install-WinGet -InstallWithDependencies $true + WriteLog "WinGet is not installed. Downloading preview version of WinGet..." + Install-WinGet } elseif (-not (Get-Command winget -ErrorAction SilentlyContinue)) { - WriteLog "WinGet is not on the path. Downloading preview version of WinGet without dependencies..." - Install-WinGet -InstallWithDependencies $false + WriteLog "WinGet is not on the path. Downloading preview version of WinGet..." + Install-WinGet } elseif (-not ((& winget.exe --version) -like "*preview*")) { - WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet without dependencies..." - Install-WinGet -InstallWithDependencies $false + WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet..." + Install-WinGet } } @@ -1730,24 +1723,21 @@ function New-WinGetSettings { if (Test-Path -Path $wingetSettingsFile -PathType Leaf) { $jsonContent = Get-Content -Path $wingetSettingsFile -Raw # Check if storeDownload feature is already enabled - if ($jsonContent -notmatch '"storeDownload"\s*:\s*true') { - # Back up existing settings.json file - $backupWingetSettingsFile = $wingetSettingsFile + ".bak" - if (-not (Test-Path -Path $backupWingetSettingsFile -PathType Leaf)) { - WriteLog "Backing up existing WinGet settings.json file to $backupWingetSettingsFile" - Copy-Item -Path $wingetSettingsFile -Destination $backupWingetSettingsFile -Force | Out-Null - } - WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" - $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force - } - else { + WriteLog "Checking if storeDownload feature is enabled in WinGet configuration." + if ($jsonContent -match '"storeDownload"\s*:\s*true') { WriteLog "WinGet's settings.json file is already configured to enable the storeDownload feature." + return + } + # Back up existing settings.json file + WriteLog "The storeDownload feature is not enabled in WinGet configuration." + $backupWingetSettingsFile = $wingetSettingsFile + ".bak" + if (-not (Test-Path -Path $backupWingetSettingsFile -PathType Leaf)) { + WriteLog "Backing up existing WinGet settings.json file to $backupWingetSettingsFile" + Copy-Item -Path $wingetSettingsFile -Destination $backupWingetSettingsFile -Force | Out-Null } } - else { - WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" - $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force - } + WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" + $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force } function Add-Win32SilentInstallCommand { @@ -1812,10 +1802,19 @@ function Get-WinGetApp { WriteLog "Downloading $WinGetAppName..." $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String if ($wingetDownloadResult -match "No applicable installer found") { + WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + if ($wingetDownloadResult -match "Installer downloaded") { + WriteLog "Downloaded $WinGetAppName without specifying architecture." + } + else { + WriteLog "No installer found for $WinGetAppName. Skipping download." + Remove-Item -Path $appFolderPath -Recurse -Force + return $false + } } if ($wingetDownloadResult -notmatch "Installer downloaded") { - WriteLog "$WinGetAppName did not successfully download." + WriteLog "No installer found for $WinGetAppName. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force return $false } From f7f52903a44563af92713eb518d9442babf2b56d Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 12 Jul 2024 00:38:48 -0400 Subject: [PATCH 27/43] Refactor Get-StoreApp for improved readability --- FFUDevelopment/BuildFFUVM.ps1 | 94 ++++++++++++++++------------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index d64bcbc..407e9a2 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1850,67 +1850,61 @@ function Get-StoreApp { $appID = ($appResult -split '\s+')[-2] # Checking app ID to determine if store app is a win32 app WriteLog "Checking if $StoreAppName is a win32 app..." - if ($appID.StartsWith("XP")) { + $appIsWin32 = $appID.StartsWith("XP") + if ($appIsWin32) { WriteLog "$StoreAppName is a win32 app. Adding to $AppsPath\win32 folder" $appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreAppName - New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - WriteLog "Downloading $StoreAppName for $WindowsArch architecture..." - $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | Out-String - if ($wingetDownloadResult -match "No applicable installer found") { - WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..." - $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match "Installer downloaded"){ - WriteLog "Downloaded $StoreAppName without specifying architecture." - } - else { - WriteLog "No installer found for $StoreAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } + } + else { + $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 -LineNumber $LineNumber # Since a Win32 app was received, returning false to increment line number for silent install command return $false } - $appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreAppName - New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - # Invoke-Process is not used here because it terminates the script if the exit code of the process is not zero. - # WinGet's download command will return a non-zero exit code when downloading store apps, as attempting to download the license file always appears to cause an error. - WriteLog "Attempting to download $StoreAppName and dependencies..." - 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.' - $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --architecture "$WindowsArch" --scope machine | 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..." - $wingetDownloadResult = & winget.exe download "$StoreAppId" --download-directory "$appFolderPath" --accept-package-agreements --accept-source-agreements --source msstore --scope machine | Out-String - if ($wingetDownloadResult -match "Microsoft Store package download completed") { - WriteLog "Downloaded $StoreAppName without specifying architecture." - # 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 - } - } + 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 } } } - else { - WriteLog "No installer found for $StoreAppName from the msstore source. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } } - elseif ($wingetDownloadResult -notmatch "Microsoft Store package download completed") { - WriteLog "Download not supported for $StoreAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return - } - Set-InstallStoreAppsFlag 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. From 1a444d8e0faa0cde465fe886df24a9d0556ec2b5 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:40:43 -0400 Subject: [PATCH 28/43] Refactor Install-WinGet function and add architecture parameter and stable WinGet download, modify Confirm-WinGetInstallation to check for stable release, refactor Get-WinGetApp for improved maintainability and readability --- FFUDevelopment/BuildFFUVM.ps1 | 87 ++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 407e9a2..f7e108c 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1659,48 +1659,44 @@ function Get-Office { } function Install-WinGet { - $wingetPreviewLink = "https://aka.ms/getwingetpreview" - $wingetPackageDestination = "$env:TEMP\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" - $dependencies = @( - @{ - Source = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx" - Destination = "$env:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx" - }, - @{ - Source = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx" - Destination = "$env:TEMP\Microsoft.UI.Xaml.2.8.x64.appx" - } + param ( + [string]$Architecture ) - foreach ($dependency in $dependencies) { - $dependencyName = [System.IO.Path]::GetFileName($dependency.Source) - WriteLog "Downloading $dependencyName..." - Start-BitsTransferWithRetry -Source $dependency.Source -Destination $dependency.Destination - WriteLog "Installing $dependencyName..." - Add-AppxPackage -Path $dependency.Destination -ErrorAction SilentlyContinue - WriteLog "Removing $dependencyName..." - Remove-Item -Path $dependency.Destination -Force -ErrorAction SilentlyContinue + $packages = @( + @{Name = "VCLibs"; Url = "https://aka.ms/Microsoft.VCLibs.$Architecture.14.00.Desktop.appx"; File = "Microsoft.VCLibs.$Architecture.14.00.Desktop.appx"}, + @{Name = "UIXaml"; Url = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.$Architecture.appx"; File = "Microsoft.UI.Xaml.2.8.$Architecture.appx"}, + @{Name = "WinGet"; Url = "https://aka.ms/getwinget"; File = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"} + ) + foreach ($package in $packages) { + $destination = Join-Path -Path $env:TEMP -ChildPath $package.File + WriteLog "Downloading $($package.Name) from $($package.Url) to $destination" + Start-BitsTransferWithRetry -Source $package.Url -Destination $destination + WriteLog "Installing $($package.Name)..." + Add-AppxPackage -Path $destination -ErrorAction SilentlyContinue + WriteLog "Removing $($package.Name)..." + Remove-Item -Path $destination -Force -ErrorAction SilentlyContinue } - WriteLog "Downloading WinGet..." - Start-BitsTransferWithRetry -Source $wingetPreviewLink -Destination $wingetPackageDestination - WriteLog "Installing WinGet..." - Add-AppxPackage -Path $wingetPackageDestination -ErrorAction SilentlyContinue - WriteLog "Removing WinGet installer..." - Remove-Item -Path $wingetPackageDestination -Force -ErrorAction SilentlyContinue + WriteLog "WinGet installation complete." } function Confirm-WinGetInstallation { $wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe" - if (-not (Test-Path $wingetPath)) { - WriteLog "WinGet is not installed. Downloading preview version of WinGet..." - Install-WinGet + $minVersion = [version]"1.8.1911" + if (-not (Test-Path -Path $wingetPath -PathType Leaf)) { + WriteLog "WinGet is not installed. Downloading WinGet..." + Install-WinGet -Architecture $WindowsArch + return } - elseif (-not (Get-Command winget -ErrorAction SilentlyContinue)) { - WriteLog "WinGet is not on the path. Downloading preview version of WinGet..." - Install-WinGet - } - elseif (-not ((& winget.exe --version) -like "*preview*")) { - WriteLog "The preview version of WinGet is not installed. Downloading preview version of WinGet..." - Install-WinGet + if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) { + WriteLog "WinGet is not on the path. Downloading WinGet..." + Install-WinGet -Architecture $WindowsArch + return + } + $wingetVersion = & winget.exe --version + 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 + return } } @@ -1800,18 +1796,25 @@ function Get-WinGetApp { $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null WriteLog "Downloading $WinGetAppName..." - $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget --architecture "$WindowsArch" | Out-String + $downloadParams = @( + "download", + "--id", "$WinGetAppId", + "--exact", + "--download-directory", "$appFolderPath", + "--accept-package-agreements", + "--accept-source-agreements", + "--source", "winget", + "--scope", "machine", + "--architecture", "$WindowsArch" + ) + $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..." - $wingetDownloadResult = & winget.exe download --id "$WinGetAppId" --exact --download-directory "$appFolderPath" --scope machine --source winget | Out-String + $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." } - else { - WriteLog "No installer found for $WinGetAppName. Skipping download." - Remove-Item -Path $appFolderPath -Recurse -Force - return $false - } } if ($wingetDownloadResult -notmatch "Installer downloaded") { WriteLog "No installer found for $WinGetAppName. Skipping download." From 191c30dd659ec83e629c6a40dcc05db6f8ab4c27 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:43:10 -0400 Subject: [PATCH 29/43] Remove New-WinGetSettings, since stable release of WinGet now supports MSStore app downloads --- FFUDevelopment/BuildFFUVM.ps1 | 37 ----------------------------------- 1 file changed, 37 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index f7e108c..8578c47 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1700,42 +1700,6 @@ function Confirm-WinGetInstallation { } } -function New-WinGetSettings { - $wingetSettingsFile = "$env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json" - $wingetSettings = @( - '{' - ' "$schema": "https://aka.ms/winget-settings.schema.json",' - ' ' - ' // For documentation on these settings, see: https://aka.ms/winget-settings' - ' "experimentalFeatures": {' - ' "storeDownload": true' - ' },' - ' "logging": {' - ' "level": "verbose"' - ' }' - '}' - ) - $wingetSettingsContent = $wingetSettings -join "`n" - if (Test-Path -Path $wingetSettingsFile -PathType Leaf) { - $jsonContent = Get-Content -Path $wingetSettingsFile -Raw - # Check if storeDownload feature is already enabled - WriteLog "Checking if storeDownload feature is enabled in WinGet configuration." - if ($jsonContent -match '"storeDownload"\s*:\s*true') { - WriteLog "WinGet's settings.json file is already configured to enable the storeDownload feature." - return - } - # Back up existing settings.json file - WriteLog "The storeDownload feature is not enabled in WinGet configuration." - $backupWingetSettingsFile = $wingetSettingsFile + ".bak" - if (-not (Test-Path -Path $backupWingetSettingsFile -PathType Leaf)) { - WriteLog "Backing up existing WinGet settings.json file to $backupWingetSettingsFile" - Copy-Item -Path $wingetSettingsFile -Destination $backupWingetSettingsFile -Force | Out-Null - } - } - WriteLog "Creating WinGet settings.json file to allow the storeDownload feature. Writing file to $wingetSettingsFile" - $wingetSettingsContent | Out-File -FilePath $wingetSettingsFile -Encoding utf8 -Force -} - function Add-Win32SilentInstallCommand { param ( [string]$AppFolder, @@ -1962,7 +1926,6 @@ function Get-Apps { } } if ($storeApps) { - New-WinGetSettings if (-not (Test-Path -Path $storeAppsFolder -PathType Container)) { New-Item -Path $storeAppsFolder -ItemType Directory -Force | Out-Null } From ab58b27a1dc4639e5025c76bd38ac9275f703991 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:40:59 -0400 Subject: [PATCH 30/43] Removed checking of appId using result parsing, since appId is already a parameter, added command to remove unprovisioned Notepad++ package, which breaks Sysprep --- FFUDevelopment/Apps/AppList.json | 5 +++++ FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 5 +++++ FFUDevelopment/BuildFFUVM.ps1 | 8 ++------ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/FFUDevelopment/Apps/AppList.json b/FFUDevelopment/Apps/AppList.json index ea7e1d7..a33c2b9 100644 --- a/FFUDevelopment/Apps/AppList.json +++ b/FFUDevelopment/Apps/AppList.json @@ -9,6 +9,11 @@ "name": "Company Portal", "id": "9WZDNCRFJ3PZ", "source": "msstore" + }, + { + "name": "Microsoft Teams", + "id": "Microsoft.Teams", + "source": "winget" } ] } \ No newline at end of file diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index 1fefc88..8b39b03 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -51,6 +51,11 @@ for /d %%D in ("%basepath%\*") do ( ) :remaining endlocal +for /r "D:\" %%G in (.) do ( + if exist "%%G\Notepad++" ( + powershell -Command "Remove-AppxPackage -Package NotepadPlusPlus_1.0.0.0_neutral__7njy0v32s6xk6" + ) +) REM The below lines will remove the unattend.xml that gets the machine into audit mode. If not removed, the OS will get stuck booting to audit mode each time. REM Also kills the sysprep process in order to automate sysprep generalize del c:\windows\panther\unattend\unattend.xml /F /Q diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 8578c47..b09e4d9 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1811,18 +1811,14 @@ function Get-StoreApp { WriteLog "$StoreAppName not found in WinGet repository. Skipping download." return } - # Skip the header lines and get the line with the app information - $appResult = $wingetSearchResult | Select-Object -Skip 2 | Select-Object -First 1 - # Split the line by whitespace and get the second-to-last item (the Id) - $appID = ($appResult -split '\s+')[-2] - # Checking app ID to determine if store app is a win32 app WriteLog "Checking if $StoreAppName is a win32 app..." - $appIsWin32 = $appID.StartsWith("XP") + $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 From ddf9c1f98664e96731491c9a34bd9d2c4279a6a0 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:46:01 -0400 Subject: [PATCH 31/43] Fix Get-Apps function parameter name --- FFUDevelopment/BuildFFUVM.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index b09e4d9..45711c2 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1890,9 +1890,9 @@ function Get-StoreApp { function Get-Apps { param ( - [string]$AppsList + [string]$AppList ) - $apps = Get-Content -Path $AppsList -Raw | ConvertFrom-Json + $apps = Get-Content -Path $AppList -Raw | ConvertFrom-Json if (-not $apps) { WriteLog "No apps were specified in AppList.json file." return @@ -3349,7 +3349,7 @@ if ($InstallApps) { exit } WriteLog "$AppsPath\InstallAppsandSysprep.cmd found" - Get-Apps -AppsList "$AppsPath\AppList.json" + Get-Apps -AppList "$AppsPath\AppList.json" if (-not $InstallOffice) { #Modify InstallAppsandSysprep.cmd to REM out the office install command $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" From f6c3c0b6c3d7dc5c18803141dc157f03f054698c Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Sat, 13 Jul 2024 08:38:09 -0700 Subject: [PATCH 32/43] create unattend files for x64 and arm64 depending on windows architecture --- .../BuildFFUUnattend/unattend_arm64.xml | 21 +++++++++++++++++++ .../{unattend.xml => unattend_x64.xml} | 0 2 files changed, 21 insertions(+) create mode 100644 FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml rename FFUDevelopment/BuildFFUUnattend/{unattend.xml => unattend_x64.xml} (100%) diff --git a/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml b/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml new file mode 100644 index 0000000..ca11e8b --- /dev/null +++ b/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml @@ -0,0 +1,21 @@ + + + + + + + 1 + d:\InstallAppsandSysprep.cmd + + + + + + + + Audit + + + + + diff --git a/FFUDevelopment/BuildFFUUnattend/unattend.xml b/FFUDevelopment/BuildFFUUnattend/unattend_x64.xml similarity index 100% rename from FFUDevelopment/BuildFFUUnattend/unattend.xml rename to FFUDevelopment/BuildFFUUnattend/unattend_x64.xml From e4e499e796f13fd0ea357f6089141c94a4c95962 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:36:14 -0700 Subject: [PATCH 33/43] create unattend files for x64 and arm64 --- .../BuildFFUUnattend/unattend_arm64.xml | 21 +++++++++++++++++++ .../{unattend.xml => unattend_x64.xml} | 0 2 files changed, 21 insertions(+) create mode 100644 FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml rename FFUDevelopment/BuildFFUUnattend/{unattend.xml => unattend_x64.xml} (100%) diff --git a/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml b/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml new file mode 100644 index 0000000..ca11e8b --- /dev/null +++ b/FFUDevelopment/BuildFFUUnattend/unattend_arm64.xml @@ -0,0 +1,21 @@ + + + + + + + 1 + d:\InstallAppsandSysprep.cmd + + + + + + + + Audit + + + + + diff --git a/FFUDevelopment/BuildFFUUnattend/unattend.xml b/FFUDevelopment/BuildFFUUnattend/unattend_x64.xml similarity index 100% rename from FFUDevelopment/BuildFFUUnattend/unattend.xml rename to FFUDevelopment/BuildFFUUnattend/unattend_x64.xml From 74fd71161ba93ef54904c0508f33699b959717b7 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT Date: Mon, 15 Jul 2024 22:55:24 -0700 Subject: [PATCH 34/43] Added files for VM Deployment for testing --- .../BuildWinPEDeploymentMediaVM_ARM64.cmd | 21 + .../WinPEDeployFFUFilesVM/ApplyFFU.ps1 | 622 ++++++++++++++++++ .../ExtendPartition-UEFI.txt | 10 + .../Windows/System32/startnet.cmd | 5 + 4 files changed, 658 insertions(+) create mode 100644 FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd create mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 create mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt create mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd new file mode 100644 index 0000000..2eee8b1 --- /dev/null +++ b/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd @@ -0,0 +1,21 @@ +rd c:\FFUDevelopment\WinPE /S /Q +cmd /c copype arm64 c:\FFUDevelopment\WinPE +Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-WMI.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-NetFX.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-Scripting.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-PowerShell.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-StorageWMI.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-DismCmdlets.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" +xcopy "C:\FFUDevelopment\WinPEDeployFFUFilesVM" c:\FFUDevelopment\WinPE\mount /Y /E +REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers +REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver: /Recurse +Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit +MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_ARM64.iso" +rd c:\FFUDevelopment\WinPE /S /Q \ No newline at end of file diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 new file mode 100644 index 0000000..d61f4e1 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 @@ -0,0 +1,622 @@ +function Get-USBDrive(){ + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter + if ($null -eq $USBDriveLetter){ + #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition + $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter + #If we didn't get the drive letter, stop the script. + if ($null -eq $USBDriveLetter){ + WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' + Exit + } + + } + $USBDriveLetter = $USBDriveLetter + ":\" + return $USBDriveLetter +} + +# function Get-HardDrive(){ +# $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID +# return $DeviceID +# } +function Get-HardDrive(){ + $DiskDrive = Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -eq 'Microsoft Virtual Disk'} + $DeviceID = $DiskDrive.DeviceID + $BytesPerSector = $Diskdrive.BytesPerSector + + # Create a custom object to return both values + $result = New-Object PSObject -Property @{ + DeviceID = $DeviceID + BytesPerSector = $BytesPerSector + } + + return $result +} + +function WriteLog($LogText){ + Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" +} + +function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ + (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile +} + +function Set-Computername($computername){ + [xml]$xml = Get-Content $UnattendFile + if($xml.unattend.settings.component.Count -ge 2){ + #Assumes that Computername is the first component element + $xml.unattend.settings.component[0].ComputerName = $computername + }else{ + $xml.unattend.settings.component.ComputerName = $computername + } + $xml.Save($UnattendFile) + return $computername +} + +function Invoke-Process { + [CmdletBinding(SupportsShouldProcess)] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + $ErrorActionPreference = 'Stop' + + try { + $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" + $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" + + $startProcessParams = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + RedirectStandardError = $stdErrTempFile + RedirectStandardOutput = $stdOutTempFile + Wait = $true; + PassThru = $true; + NoNewWindow = $false; + } + if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { + $cmd = Start-Process @startProcessParams + $cmdOutput = Get-Content -Path $stdOutTempFile -Raw + $cmdError = Get-Content -Path $stdErrTempFile -Raw + if ($cmd.ExitCode -ne 0) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + } else { + if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { + WriteLog $cmdOutput + } + } + } + } catch { + #$PSCmdlet.ThrowTerminatingError($_) + WriteLog $_ + Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' + throw $_ + + } finally { + Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore + + } + +} + +# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have +# a driver for the battery installed, so you'll need to inject drivers, which can be tricky because just injecting the battery driver might not be enough, +# you might also need other drivers that the battery driver is dependent on. +# function Get-Battery(){ +# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") +# { +# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." +# Start-Sleep 60 +# } + +# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" +# } + +#Get USB Drive and create log file +$LogFileName = 'ScriptLog.txt' +$USBDrive = Get-USBDrive +New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null +$LogFile = $USBDrive + $LogFilename +$version = '2309.2' +WriteLog 'Begin Logging' +WriteLog "Script version: $version" + +#Find PhysicalDrive +$hardDrive = Get-HardDrive +# $PhysicalDeviceID = $hardDrive.DeviceID +$BytesPerSector = $hardDrive.BytesPerSector +# WriteLog "Physical DeviceID is $PhysicalDeviceID" +# WriteLog "Physical BytesPerSector is $BytesPerSector" +$PhysicalDeviceID = '\\.\PHYSICALDRIVE0' +WriteLog "Physical DeviceID is $PhysicalDeviceID" + + +#Parse DiskID Number +# $DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) +# WriteLog "DiskID is $DiskID" +$DiskID = '0' +WriteLog "DiskID is $DiskID" + +#COMMENT THIS WHOLE BLOCK OUT ONCE FFUPROVIDER FIX IS IN +#Modify diskpart answer files if DiskID not 0 +# $UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' +$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' + +If ($DiskID -ne '0'){ + WriteLog 'DiskID is not 0. Need to modify diskpart answer files' + # try { + # Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID + # } + # catch { + # WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" + # } + + try { + Set-DiskpartAnswerFiles $ExtendPartition $DiskID + } + catch { + WriteLog "Modifying $ExtendPartition failed with error: $_" + } +} + +#Find FFU Files +[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) +$FFUCount = $FFUFiles.Count + +#If multiple FFUs found, ask which to install +If ($FFUCount -gt 1) { + WriteLog "Found $FFUCount FFU Files" + $array = @() + + for($i=0;$i -le $FFUCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, FFUFile + do { + try { + $var = $true + [int]$FFUSelected = Read-Host 'Enter the FFU number to install' + $FFUSelected = $FFUSelected -1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid FFU number' + $var = $false + } + } until (($FFUSelected -le $FFUCount -1) -and $var) + + $FFUFileToInstall = $array[$FFUSelected].FFUFile + WriteLog "$FFUFileToInstall was selected" +} +elseif ($FFUCount -eq 1) { + WriteLog "Found $FFUCount FFU File" + $FFUFileToInstall = $FFUFiles[0].FullName + WriteLog "$FFUFileToInstall will be installed" +} +else { + Writelog 'No FFU files found' + Write-Host 'No FFU files found' + Exit +} + +#FindAP +$APFolder = $USBDrive + "Autopilot\" +If (Test-Path -Path $APFolder){ + [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) + $APFilesCount = $APFiles.Count + if ($APFilesCount -ge 1){ + $autopilot = $true + } +} + + +#FindPPKG +$PPKGFolder = $USBDrive + "PPKG\" +if (Test-Path -Path $PPKGFolder){ + [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) + $PPKGFilesCount = $PPKGFiles.Count + if ($PPKGFilesCount -ge 1){ + $PPKG = $true + } +} + +#FindUnattend +$UnattendFolder = $USBDrive + "unattend\" +$UnattendFilePath = $UnattendFolder + "unattend.xml" +$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" +If (Test-Path -Path $UnattendFilePath){ + $UnattendFile = Get-ChildItem -Path $UnattendFilePath + If ($UnattendFile){ + $Unattend = $true + } +} +If (Test-Path -Path $UnattendPrefixPath){ + $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath + If ($UnattendPrefixFile){ + $UnattendPrefix = $true + } +} + +#Ask for device name if unattend exists +if ($Unattend -and $UnattendPrefix){ + Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' + $UnattendPrefixes = @(Get-content $UnattendPrefixFile) + $UnattendPrefixCount = $UnattendPrefixes.Count + If ($UnattendPrefixCount -gt 1) { + WriteLog "Found $UnattendPrefixCount Prefixes" + $array = @() + for($i=0;$i -le $UnattendPrefixCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix + do { + try { + $var = $true + [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' + $PrefixSelected = $PrefixSelected -1 + } + catch { + Write-Host 'Input was not in correct format. Please enter a valid prefix number' + $var = $false + } + } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) + $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix + WriteLog "$PrefixToUse was selected" + } + elseif ($UnattendPrefixCount -eq 1) { + WriteLog "Found $UnattendPrefixCount Prefix" + $PrefixToUse = $UnattendPrefixes[0] + WriteLog "Will use $PrefixToUse as device name prefix" + } + #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace + $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() + #Combine prefix with serial + $computername = $PrefixToUse + $serial + #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it + If ($computername.Length -gt 15){ + $computername = $computername.substring(0,15) + } + $computername = Set-Computername($computername) + Writelog "Computer name set to $computername" +} +elseif($Unattend){ + Writelog 'Unattend file found with no prefixes.txt, asking for name' + [string]$computername = Read-Host 'Enter device name' + Set-Computername($computername) + Writelog "Computer name set to $computername" +} +else { + WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' +} + +#If both AP and PPKG folder found with files, ask which to use. +If($autopilot -eq $true -and $PPKG -eq $true){ + WriteLog 'Both PPKG and Autopilot json files found' + Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' + do { + try { + $var = $true + [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' + } + + catch { + Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' + $var = $false + } + } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) + If ($APorPPKG -eq 1){ + $PPKG = $false + } + else{ + $autopilot = $false + } +} + +#If multiple AP json files found, ask which to install +If ($APFilesCount -gt 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount Autopilot json Files" + $array = @() + + for($i=0;$i -le $APFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, APFileName + do { + try { + $var = $true + [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' + $APFileSelected = $APFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid AP json file number' + $var = $false + } + } until (($APFileSelected -le $APFilesCount -1) -and $var) + + $APFileToInstall = $array[$APFileSelected].APFile + $APFileName = $array[$APFileSelected].APFileName + WriteLog "$APFileToInstall was selected" +} +elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { + WriteLog "Found $APFilesCount AP File" + $APFileToInstall = $APFiles[0].FullName + $APFileName = $APFiles[0].Name + WriteLog "$APFileToInstall will be copied" +} +else { + Writelog 'No AP files found or AP was not selected' +} + +#If multiple PPKG files found, ask which to install +If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG Files" + $array = @() + + for($i=0;$i -le $PPKGFilesCount -1;$i++){ + $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, PPKGFileName + do { + try { + $var = $true + [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' + $PPKGFileSelected = $PPKGFileSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' + $var = $false + } + } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) + + $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile + WriteLog "$PPKGFileToInstall was selected" +} +elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { + WriteLog "Found $PPKGFilesCount PPKG File" + $PPKGFileToInstall = $PPKGFiles[0].FullName + WriteLog "$PPKGFileToInstall will be used" +} +else { + Writelog 'No PPKG files found or PPKG not selected.' +} + +#Find Drivers +$Drivers = $USBDrive + "Drivers" +If (Test-Path -Path $Drivers) +{ + #Check if multiple driver folders found, if so, just select one folder to save time/space + $DriverFolders = Get-ChildItem -Path $Drivers + $DriverFoldersCount = $DriverFolders.count + If ($DriverFoldersCount -gt 1) + { + WriteLog "Found $DriverFoldersCount driver folders" + $array = @() + + for($i=0; $i -le $DriverFoldersCount -1; $i++){ + $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} + $array += New-Object PSObject -Property $Properties + } + $array | Format-Table -AutoSize -Property Number, Drivers + do { + try { + $var = $true + [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' + $DriversSelected = $DriversSelected - 1 + } + + catch { + Write-Host 'Input was not in correct format. Please enter a valid driver folder number' + $var = $false + } + } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) + + $Drivers = $array[$DriversSelected].Drivers + WriteLog "$Drivers was selected" + } + elseif ($DriverFoldersCount -eq 1) { + WriteLog "Found $DriverFoldersCount driver folder" + $Drivers = $DriverFolders.FullName + WriteLog "$Drivers will be installed" + } + else { + Writelog 'No driver folders found' + } +} + +#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script +#Get-Battery + +#Partition drive +Writelog 'Clean Disk' +#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append +#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" +try { + $Disk = Get-Disk -Number $DiskID + $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false +} +catch { + WriteLog 'Cleaning disk failed. Exiting' + throw $_ +} + +Writelog 'Cleaning Disk succeeded' + +#Apply FFU +WriteLog "Applying FFU to $PhysicalDeviceID" +WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" +#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. +dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully applied FFU' +} +elseif($LASTEXITCODE -eq 1393){ + WriteLog "Failed to apply FFU - LastExitCode = $LastExitCode" + WriteLog "This is likely due to a mismatched LogicalSectorByteSize" + WriteLog "BytesPerSector value from Win32_Diskdrive is $BytesPerSector" + if ($BytesPerSector -eq 4096){ + WriteLog "The FFU build process by default uses a 512 LogicalSectorByteSize. Rebuild the FFU by adding -LogicalSectorByteSize 4096 to the command line" + } + elseif($BytesPerSector -eq 512){ + WriteLog "This FFU was likely built with a LogicalSectorByteSize of 4096. Rebuild the FFU by adding -LogicalSectorByteSize 512 to the command line" + } + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} +else{ + Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} + +#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed +# $disk = get-disk -Number $DiskID +# $RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'} +# if ($RecoveryPartition){ +# $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber +# if ($RecoveryPartitionNumber -eq 4){ +# try { +# WriteLog 'Removing recovery partition' +# Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false +# } +# catch { +# WriteLog 'Error removing recovery partition, exiting' +# throw $_ +# } +# } +# else{ +# WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.' +# exit +# } +# } + +#COMMENT THIS WHOLE BLOCK OUT AFTER FFUPROVIDER FIX IS IN +# Extend Windows partition and create recovery partition +Writelog 'Extending Windows partition' +Invoke-Process diskpart.exe "/S $ExtendPartition" +if($LASTEXITCODE -eq 0){ + WriteLog 'Successfully extended Windows partition and created recovery partition' +} +else{ + Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE" +} + +#UNCOMMENT THIS AFTER FFUPROVIDER FIX IS IN +#Set W: drive letter to Windows partition +#Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object PartitionNumber -eq 3 | Set-Partition -NewDriveLetter W + +#Copy modified WinRE if folder exists, else copy inbox WinRE +$WinRE = $USBDrive + "WinRE\winre.wim" +If (Test-Path -Path $WinRE) +{ + WriteLog 'Copying modified WinRE to Recovery directory' + Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" + WriteLog 'Copying WinRE to Recovery directory succeeded' + WriteLog 'Registering location of recovery tools' + Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" + WriteLog 'Registering location of recovery tools succeeded' +} +# else +# { +# WriteLog 'Copying default WinRE to Recovery directory' +# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" +# WriteLog 'Copying WinRE to Recovery directory succeeded' +# WriteLog 'Registering location of recovery tools' +# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" +# WriteLog 'Registering location of recovery tools succeeded' +# } + +#Autopilot JSON +If ($APFileToInstall){ + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" + Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" + WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" + # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json + try { + Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' + WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" + } + + catch{ + Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" + throw $_ + } +} +#Apply PPKG +If ($PPKGFileToInstall){ + try { + #Make sure to delete any existing PPKG on the USB drive + Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { + Remove-item -Path $_.FullName + } + WriteLog "Copying $PPKGFileToInstall to $USBDrive" + Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" + WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" + } + + catch{ + Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" + throw $_ + } +} +#Set DeviceName +If ($computername){ + try{ + $PantherDir = 'w:\windows\panther' + If (Test-Path -Path $PantherDir){ + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-process xcopy "$UnattendFile $PantherDir /Y" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + else{ + Writelog "$PantherDir doesn't exist, creating it" + New-Item -Path $PantherDir -ItemType Directory -Force + Writelog "Copying $UnattendFile to $PantherDir" + Invoke-Process xcopy.exe "$UnattendFile $PantherDir" + WriteLog "Copying $UnattendFile to $PantherDir succeeded" + } + } + catch{ + WriteLog "Copying Unattend.xml to name device failed" + throw $_ + } +} + +#Add Drivers +#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. +If (Test-Path -Path $Drivers) +{ + WriteLog 'Copying drivers' + Write-Warning 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. This is done so drivers are logged to the scriptlog.txt file. Please be patient.' + Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" + WriteLog 'Copying drivers succeeded' +} + +#Copy DISM log to USBDrive +WriteLog "Copying dism log to $USBDrive" +invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" +WriteLog "Copying dism log to $USBDrive succeeded" + + + + diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt b/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt new file mode 100644 index 0000000..dbfe4b9 --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt @@ -0,0 +1,10 @@ +select disk 0 +select partition 3 +Assign letter="W" +shrink minimum=1000 +create partition primary +format quick fs=ntfs label="Recovery" +assign letter="R" +set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" +gpt attributes=0x8000000000000001 +exit diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd b/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd new file mode 100644 index 0000000..12519ba --- /dev/null +++ b/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd @@ -0,0 +1,5 @@ +wpeinit +powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c +powershell -Noprofile -ExecutionPolicy Bypass -File x:\ApplyFFU.ps1 +exit + From a5c38fd09befc2b62899ba2c3ff0572637321bf3 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:48:43 -0700 Subject: [PATCH 35/43] modified VM deployment iso cmd scripts and fixed appx license file issue --- FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 9 +++++--- .../BuildWinPEDeploymentMediaVM_ARM64.cmd | 2 +- .../BuildWinPEDeploymentMediaVM_amd64.cmd | 21 +++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index 8b39b03..df50ea8 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -29,9 +29,9 @@ for /d %%D in ("%basepath%\*") do ( ) ) ) - for %%F in ("!appfolder!\*.xml") do ( - set "licensefile=%%F" - ) + @REM for %%F in ("!appfolder!\*.xml") do ( + @REM set "licensefile=%%F" + @REM ) if defined mainpackage ( set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!"" if exist "!dependenciesfolder!" ( @@ -39,6 +39,9 @@ for /d %%D in ("%basepath%\*") do ( set "dism_command=!dism_command! /DependencyPackagePath:"%%G"" ) ) + for %%F in ("!appfolder!\*.xml") do ( + set "licensefile=%%F" + ) if defined licensefile ( set "dism_command=!dism_command! /LicensePath:"!licensefile!"" ) else ( diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd index 2eee8b1..bcc7003 100644 --- a/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd +++ b/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd @@ -17,5 +17,5 @@ xcopy "C:\FFUDevelopment\WinPEDeployFFUFilesVM" c:\FFUDevelopment\WinPE\mount /Y REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver: /Recurse Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit -MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_ARM64.iso" +MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_arm64.iso" rd c:\FFUDevelopment\WinPE /S /Q \ No newline at end of file diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd new file mode 100644 index 0000000..8f490d4 --- /dev/null +++ b/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd @@ -0,0 +1,21 @@ +rd c:\FFUDevelopment\WinPE /S /Q +cmd /c copype amd64 c:\FFUDevelopment\WinPE +Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab" +Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" +xcopy "C:\FFUDevelopment\WinPEDeployFFUFilesVM" c:\FFUDevelopment\WinPE\mount /Y /E +REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers +REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver: /Recurse +Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit +MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_amd64.iso" +rd c:\FFUDevelopment\WinPE /S /Q \ No newline at end of file From 8100df3d24b0776335120c95e0182322cfa98ae3 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:43:35 -0400 Subject: [PATCH 36/43] Add diskpart commands to assign GPT attributes to recovery partition --- FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 index ec131e6..e3e3a5f 100644 --- a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -444,6 +444,16 @@ WriteLog "Applying FFU to $PhysicalDeviceID" WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" #In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID +$recoveryPartition = Get-Partition -Disk $Disk | Where-Object PartitionNumber -eq 4 +if ($recoveryPartition) { + $diskpartScript = @( + "SELECT DISK $($Disk.Number)", + "SELECT PARTITION $($recoveryPartition.PartitionNumber)", + "GPT ATTRIBUTES=0x8000000000000001", + "EXIT" + ) + $diskpartScript | diskpart.exe +} if($LASTEXITCODE -eq 0){ WriteLog 'Successfully applied FFU' } From 5616082275619bd63e3b3ddfdd7882a468c1b1c6 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:14:00 -0400 Subject: [PATCH 37/43] Add conditional check to not run clear-disk on new drives that are uninitialized --- FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 index e3e3a5f..b100eb2 100644 --- a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -430,7 +430,9 @@ Writelog 'Clean Disk' #Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" try { $Disk = Get-Disk -Number $DiskID - $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false + if ($Disk.PartitionStyle -ne "RAW") { + $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false + } } catch { WriteLog 'Cleaning disk failed. Exiting' From f29e3c434951c9f25ca3df20fd1e0b2192c4d048 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:35:15 -0700 Subject: [PATCH 38/43] added additional winget app logging, removed static linenumber references --- FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 1 + FFUDevelopment/BuildFFUVM.ps1 | 80 +++++++++++-------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index df50ea8..2178783 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -7,6 +7,7 @@ REM Install Defender Definitions REM Install Windows Security Platform Update REM Install OneDrive Per Machine REM Install Edge Stable +REM Winget Win32 Apps REM Add additional apps below here REM Contoso App (Example) REM msiexec /i d:\Contoso\setup.msi /qn /norestart diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 45711c2..5228515 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1680,6 +1680,7 @@ function Install-WinGet { } 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)) { @@ -1688,11 +1689,12 @@ function Confirm-WinGetInstallation { return } if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) { - WriteLog "WinGet is not on the path. Downloading WinGet..." + WriteLog "WinGet not found. Downloading WinGet..." Install-WinGet -Architecture $WindowsArch return } $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 @@ -1703,8 +1705,7 @@ function Confirm-WinGetInstallation { function Add-Win32SilentInstallCommand { param ( [string]$AppFolder, - [string]$AppFolderPath, - [int]$LineNumber + [string]$AppFolderPath ) $appName = $AppFolder $installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop @@ -1730,9 +1731,9 @@ function Add-Win32SilentInstallCommand { } $cmdFile = "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent = Get-Content -Path $cmdFile - $cmdContent = $cmdContent[0..($lineNumber - 2)] + $silentInstallCommand.Trim() + $cmdContent[($lineNumber - 1)..($cmdContent.Length - 1)] - WriteLog "Writing silent install command for $appName to InstallAppsandSysprep.cmd at line number $LineNumber" - Set-Content -Path $cmdFile -Value $cmdContent + $UpdatedcmdContent = $CmdContent -replace '^(REM Winget Win32 Apps)', ("REM Winget Win32 Apps`r`nREM Win32 $($AppName)`r`n$($silentInstallCommand.Trim())") + WriteLog "Writing silent install command for $appName to InstallAppsandSysprep.cmd" + Set-Content -Path $cmdFile -Value $UpdatedcmdContent } function Set-InstallStoreAppsFlag { @@ -1748,18 +1749,16 @@ function Set-InstallStoreAppsFlag { function Get-WinGetApp { param ( [string]$WinGetAppName, - [string]$WinGetAppId, - [int]$LineNumber + [string]$WinGetAppId ) $wingetSearchResult = & winget.exe search --id "$WinGetAppId" --exact --accept-source-agreements --source winget if ($wingetSearchResult -contains "No package found matching input criteria.") { WriteLog "$WinGetAppName not found in WinGet repository. Skipping download." - # Return false to not increment line number for silent install command. - return $false } $appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName + WriteLog "Creating $appFolderPath" New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null - WriteLog "Downloading $WinGetAppName..." + WriteLog "Downloading $WinGetAppName to $appFolderPath" $downloadParams = @( "download", "--id", "$WinGetAppId", @@ -1771,6 +1770,7 @@ function Get-WinGetApp { "--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..." @@ -1783,28 +1783,31 @@ function Get-WinGetApp { if ($wingetDownloadResult -notmatch "Installer downloaded") { WriteLog "No installer found for $WinGetAppName. Skipping download." Remove-Item -Path $appFolderPath -Recurse -Force - return $false } - WriteLog "$WinGetAppName has completed downloading to $appFolderPath" + 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 - return $false } else { - Add-Win32SilentInstallCommand -AppFolder $WinGetAppName -AppFolderPath $appFolderPath -LineNumber $LineNumber + Add-Win32SilentInstallCommand -AppFolder $WinGetAppName -AppFolderPath $appFolderPath } } function Get-StoreApp { param ( [string]$StoreAppName, - [string]$StoreAppId, - [int]$LineNumber + [string]$StoreAppId ) $wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore if ($wingetSearchResult -contains "No package found matching input criteria.") { @@ -1850,9 +1853,7 @@ function Get-StoreApp { return } if ($appIsWin32) { - Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath -LineNumber $LineNumber - # Since a Win32 app was received, returning false to increment line number for silent install command - return $false + Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath } Set-InstallStoreAppsFlag # If $WindowsArch -eq 'ARM64', remove all dependency files that are not ARM64 @@ -1898,22 +1899,33 @@ function Get-Apps { return } $wingetApps = $apps.apps | Where-Object { $_.source -eq "winget" } - $storeApps = $apps.apps | Where-Object { $_.source -eq "msstore" } + # List each Winget app in the AppList.json file + if ($wingetApps) { + WriteLog 'Winget apps to be installed:' + foreach ($wingetapp in $wingetApps){ + WriteLog "$($wingetapp.Name)" + } + } + $StoreApps = $apps.apps | Where-Object { $_.source -eq "msstore" } + # List each Store app in the AppList.json file + if ($StoreApps) { + WriteLog 'Store apps to be installed:' + foreach ($StoreApp in $StoreApps){ + WriteLog "$($StoreApp.Name)" + } + } Confirm-WinGetInstallation - $lineNumber = 13 $win32Folder = Join-Path -Path $AppsPath -ChildPath "Win32" $storeAppsFolder = Join-Path -Path $AppsPath -ChildPath "MSStore" if ($wingetApps) { if (-not (Test-Path -Path $win32Folder -PathType Container)) { + WriteLog "Creating folder for Winget Win32 apps: $win32Folder" New-Item -Path $win32Folder -ItemType Directory -Force | Out-Null + WriteLog "Folder created successfully." } foreach ($wingetApp in $wingetApps) { try { - $result = Get-WinGetApp -WinGetAppName $wingetApp.Name -WinGetAppId $wingetApp.Id -LineNumber $lineNumber - if ($null -eq $result) { - $lineNumber++ - WriteLog "Line number incremented to $lineNumber" - } + Get-WinGetApp -WinGetAppName $wingetApp.Name -WinGetAppId $wingetApp.Id } catch { WriteLog "Error occurred while processing $wingetApp : $_" @@ -1927,11 +1939,7 @@ function Get-Apps { } foreach ($storeApp in $storeApps) { try { - $result = Get-StoreApp -StoreAppName $storeApp.Name -StoreAppId $storeApp.Id -LineNumber $lineNumber - if ($result -eq $false) { - $lineNumber++ - WriteLog "Line number incremented to $lineNumber" - } + Get-StoreApp -StoreAppName $storeApp.Name -StoreAppId $storeApp.Id } catch { WriteLog "Error occurred while processing $storeApp : $_" @@ -3190,6 +3198,8 @@ function Remove-FFU { function Clear-InstallAppsandSysprep { $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove win32 app install commands" + $cmdContent -notmatch "REM Win32*" | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent -notmatch "D:\\win32*" | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $cmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" WriteLog "Setting MSStore installation condition to false" @@ -3349,7 +3359,11 @@ if ($InstallApps) { exit } WriteLog "$AppsPath\InstallAppsandSysprep.cmd found" - Get-Apps -AppList "$AppsPath\AppList.json" + If (Test-Path -Path "$AppsPath\AppList.json"){ + WriteLog "$AppsPath\AppList.json found, checking for winget apps to install" + Get-Apps -AppList "$AppsPath\AppList.json" + } + if (-not $InstallOffice) { #Modify InstallAppsandSysprep.cmd to REM out the office install command $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" From 5600b2fbbd1461e5f01ac63fd12c5649edf1dc67 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Fri, 19 Jul 2024 19:17:02 -0700 Subject: [PATCH 39/43] fixed issues creating deploy media for VMs, cleaned up some old commented code in ApplyFFU.ps1 --- FFUDevelopment/BuildFFUVM.ps1 | 17 +- .../BuildWinPEDeploymentMediaVM_ARM64.cmd | 21 - .../BuildWinPEDeploymentMediaVM_amd64.cmd | 21 - .../WinPEDeployFFUFiles/ApplyFFU.ps1 | 127 ++-- .../ExtendPartition-UEFI.txt | 10 - .../WinPEDeployFFUFilesVM/ApplyFFU.ps1 | 622 ------------------ .../ExtendPartition-UEFI.txt | 10 - .../Windows/System32/startnet.cmd | 5 - 8 files changed, 64 insertions(+), 769 deletions(-) delete mode 100644 FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd delete mode 100644 FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd delete mode 100644 FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt delete mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 delete mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt delete mode 100644 FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 5228515..4c53eed 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -334,8 +334,8 @@ else { # Set default values for variables that depend on other parameters if (-not $AppsISO) { $AppsISO = "$FFUDevelopmentPath\Apps.iso" } if (-not $AppsPath) { $AppsPath = "$FFUDevelopmentPath\Apps" } -if (-not $DeployISO) { $DeployISO = "$FFUDevelopmentPath\WinPE_FFU_Deploy.iso" } -if (-not $CaptureISO) { $CaptureISO = "$FFUDevelopmentPath\WinPE_FFU_Capture.iso" } +if (-not $DeployISO) { $DeployISO = "$FFUDevelopmentPath\WinPE_FFU_Deploy_$WindowsArch.iso" } +if (-not $CaptureISO) { $CaptureISO = "$FFUDevelopmentPath\WinPE_FFU_Capture_$WindowsArch.iso" } if (-not $OfficePath) { $OfficePath = "$AppsPath\Office" } if (-not $rand) { $rand = Get-Random } if (-not $VMLocation) { $VMLocation = "$FFUDevelopmentPath\VM" } @@ -2627,7 +2627,8 @@ function New-PEMedia { WriteLog "Copy complete" #Remove Bootfix.bin - for BIOS systems, shouldn't be needed, but doesn't hurt to remove for our purposes #Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null - $WinPEISOName = 'WinPE_FFU_Capture.iso' + # $WinPEISOName = 'WinPE_FFU_Capture.iso' + $WinPEISOFile = $CaptureISO $Capture = $false } If ($Deploy) { @@ -2645,7 +2646,9 @@ function New-PEMedia { } WriteLog "Adding drivers complete" } - $WinPEISOName = 'WinPE_FFU_Deploy.iso' + # $WinPEISOName = 'WinPE_FFU_Deploy.iso' + $WinPEISOFile = $DeployISO + $Deploy = $false } WriteLog 'Dismounting WinPE media' @@ -2659,14 +2662,14 @@ function New-PEMedia { $OSCDIMGPath = "$adkPath`Assessment and Deployment Kit\Deployment Tools\arm64\Oscdimg" } $OSCDIMG = "$OSCDIMGPath\oscdimg.exe" - WriteLog "Creating WinPE ISO at $FFUDevelopmentPath\$WinPEISOName" + WriteLog "Creating WinPE ISO at $WinPEISOFile" # & "$OSCDIMG" -m -o -u2 -udfver102 -bootdata:2`#p0,e,b$OSCDIMGPath\etfsboot.com`#pEF,e,b$OSCDIMGPath\Efisys_noprompt.bin $WinPEFFUPath\media $FFUDevelopmentPath\$WinPEISOName | Out-null if($WindowsArch -eq 'x64'){ - $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$FFUDevelopmentPath\$WinPEISOName`"" + $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`"" } elseif($WindowsArch -eq 'arm64'){ - $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$FFUDevelopmentPath\$WinPEISOName`"" + $OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`"" } Invoke-Process $OSCDIMG $OSCDIMGArgs WriteLog "ISO created successfully" diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd deleted file mode 100644 index bcc7003..0000000 --- a/FFUDevelopment/BuildWinPEDeploymentMediaVM_ARM64.cmd +++ /dev/null @@ -1,21 +0,0 @@ -rd c:\FFUDevelopment\WinPE /S /Q -cmd /c copype arm64 c:\FFUDevelopment\WinPE -Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-WMI.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-NetFX.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-Scripting.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-PowerShell.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-StorageWMI.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\WinPE-DismCmdlets.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\arm64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" -xcopy "C:\FFUDevelopment\WinPEDeployFFUFilesVM" c:\FFUDevelopment\WinPE\mount /Y /E -REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers -REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver: /Recurse -Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit -MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_arm64.iso" -rd c:\FFUDevelopment\WinPE /S /Q \ No newline at end of file diff --git a/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd b/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd deleted file mode 100644 index 8f490d4..0000000 --- a/FFUDevelopment/BuildWinPEDeploymentMediaVM_amd64.cmd +++ /dev/null @@ -1,21 +0,0 @@ -rd c:\FFUDevelopment\WinPE /S /Q -cmd /c copype amd64 c:\FFUDevelopment\WinPE -Dism /Mount-Image /ImageFile:"c:\FFUDevelopment\WinPE\media\sources\boot.wim" /Index:1 /MountDir:"c:\FFUDevelopment\WinPE\mount" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-WMI.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-WMI_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-NetFX.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-NetFX_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-Scripting.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-Scripting_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-PowerShell.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-PowerShell_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-StorageWMI.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-StorageWMI_en-us.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\WinPE-DismCmdlets.cab" -Dism /Add-Package /Image:"c:\FFUDevelopment\WinPE\mount" /PackagePath:"C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs\en-us\WinPE-DismCmdlets_en-us.cab" -xcopy "C:\FFUDevelopment\WinPEDeployFFUFilesVM" c:\FFUDevelopment\WinPE\mount /Y /E -REM If you need to add drivers, remove the REM from the below line and change the /Driver:Path to a folder of drivers -REM dism /image:C:\FFUDevelopment\WinPE\mount /Add-Driver /Driver: /Recurse -Dism /Unmount-Image /MountDir:c:\FFUDevelopment\WinPE\mount /Commit -MakeWinPEMedia /ISO /F c:\FFUDevelopment\WinPE "c:\FFUDevelopment\WinPE_FFU_Deploy_VM_amd64.iso" -rd c:\FFUDevelopment\WinPE /S /Q \ No newline at end of file diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 index c1b19b4..0b38ee5 100644 --- a/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 +++ b/FFUDevelopment/WinPEDeployFFUFiles/ApplyFFU.ps1 @@ -14,9 +14,39 @@ function Get-USBDrive(){ return $USBDriveLetter } +# function Get-HardDrive(){ +# $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID +# return $DeviceID +# } function Get-HardDrive(){ - $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID - return $DeviceID + $SystemInfo = Get-WmiObject -Class 'Win32_ComputerSystem' + $Manufacturer = $SystemInfo.Manufacturer + $Model = $SystemInfo.Model + WriteLog "Device Manufacturer: $Manufacturer" + WriteLog "Device Model: $Model" + WriteLog 'Getting Hard Drive info' + if ($Manufacturer -eq 'Microsoft Corporation' -and $Model -eq 'Virtual Machine'){ + WriteLog 'Running in a Hyper-V VM. Getting virtual disk on Index 0 and SCSILogicalUnit 0' + $DiskDrive = Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' ` + -and $_.Model -eq 'Microsoft Virtual Disk' ` + -and $_.Index -eq 0 ` + -and $_.SCSILogicalUnit -eq 0 + } + } + else{ + WriteLog 'Not running in a VM. Getting physical disk drive' + $DiskDrive = Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'} + } + $DeviceID = $DiskDrive.DeviceID + $BytesPerSector = $Diskdrive.BytesPerSector + + # Create a custom object to return both values + $result = New-Object PSObject -Property @{ + DeviceID = $DeviceID + BytesPerSector = $BytesPerSector + } + + return $result } function WriteLog($LogText){ @@ -122,35 +152,17 @@ WriteLog 'Begin Logging' WriteLog "Script version: $version" #Find PhysicalDrive -$PhysicalDeviceID = Get-HardDrive +# $PhysicalDeviceID = Get-HardDrive +$hardDrive = Get-HardDrive +$PhysicalDeviceID = $hardDrive.DeviceID +$BytesPerSector = $hardDrive.BytesPerSector +WriteLog "Physical BytesPerSector is $BytesPerSector" WriteLog "Physical DeviceID is $PhysicalDeviceID" #Parse DiskID Number $DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) WriteLog "DiskID is $DiskID" -#COMMENT THIS WHOLE BLOCK OUT ONCE FFUPROVIDER FIX IS IN -# #Modify diskpart answer files if DiskID not 0 -# # $UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' -# $ExtendPartition = 'x:\ExtendPartition-UEFI.txt' - -# If ($DiskID -ne '0'){ -# WriteLog 'DiskID is not 0. Need to modify diskpart answer files' -# # try { -# # Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID -# # } -# # catch { -# # WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" -# # } - -# try { -# Set-DiskpartAnswerFiles $ExtendPartition $DiskID -# } -# catch { -# WriteLog "Modifying $ExtendPartition failed with error: $_" -# } -# } - #Find FFU Files [array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) $FFUCount = $FFUFiles.Count @@ -426,8 +438,6 @@ If (Test-Path -Path $Drivers) #Partition drive Writelog 'Clean Disk' -#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append -#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" try { $Disk = Get-Disk -Number $DiskID if ($Disk.PartitionStyle -ne "RAW") { @@ -448,58 +458,39 @@ WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDri dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID $recoveryPartition = Get-Partition -Disk $Disk | Where-Object PartitionNumber -eq 4 if ($recoveryPartition) { + WriteLog 'Setting recovery partition attributes' $diskpartScript = @( "SELECT DISK $($Disk.Number)", "SELECT PARTITION $($recoveryPartition.PartitionNumber)", "GPT ATTRIBUTES=0x8000000000000001", "EXIT" ) - $diskpartScript | diskpart.exe + $diskpartScript | diskpart.exe | Out-Null + WriteLog 'Setting recovery partition attributes complete' } if($LASTEXITCODE -eq 0){ WriteLog 'Successfully applied FFU' } +elseif($LASTEXITCODE -eq 1393){ + WriteLog "Failed to apply FFU - LastExitCode = $LastExitCode" + WriteLog "This is likely due to a mismatched LogicalSectorByteSize" + WriteLog "BytesPerSector value from Win32_Diskdrive is $BytesPerSector" + if ($BytesPerSector -eq 4096){ + WriteLog "The FFU build process by default uses a 512 LogicalSectorByteSize. Rebuild the FFU by adding -LogicalSectorByteSize 4096 to the command line" + } + elseif($BytesPerSector -eq 512){ + WriteLog "This FFU was likely built with a LogicalSectorByteSize of 4096. Rebuild the FFU by adding -LogicalSectorByteSize 512 to the command line" + } + #Copy DISM log to USBDrive + invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" + exit +} else{ Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" #Copy DISM log to USBDrive invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" exit } - -#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed -# $disk = get-disk -Number $DiskID -# $RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'} -# if ($RecoveryPartition){ -# $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber -# if ($RecoveryPartitionNumber -eq 4){ -# try { -# WriteLog 'Removing recovery partition' -# Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false -# } -# catch { -# WriteLog 'Error removing recovery partition, exiting' -# throw $_ -# } -# } -# else{ -# WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.' -# exit -# } -# } - -#COMMENT THIS WHOLE BLOCK OUT AFTER FFUPROVIDER FIX IS IN -# # Extend Windows partition and create recovery partition -# Writelog 'Extending Windows partition' -# Invoke-Process diskpart.exe "/S $ExtendPartition" -# if($LASTEXITCODE -eq 0){ -# WriteLog 'Successfully extended Windows partition and created recovery partition' -# } -# else{ -# Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE" -# } - -#UNCOMMENT THIS AFTER FFUPROVIDER FIX IS IN -# Set W: drive letter to Windows partition Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object PartitionNumber -eq 3 | Set-Partition -NewDriveLetter W #Copy modified WinRE if folder exists, else copy inbox WinRE @@ -515,16 +506,6 @@ If (Test-Path -Path $WinRE) Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object Type -eq Recovery | Remove-PartitionAccessPath -AccessPath R: WriteLog 'Registering location of recovery tools succeeded' } -# else -# { -# WriteLog 'Copying default WinRE to Recovery directory' -# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" -# WriteLog 'Copying WinRE to Recovery directory succeeded' -# WriteLog 'Registering location of recovery tools' -# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" -# WriteLog 'Registering location of recovery tools succeeded' -# } - #Autopilot JSON If ($APFileToInstall){ WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" diff --git a/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt b/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt deleted file mode 100644 index dbfe4b9..0000000 --- a/FFUDevelopment/WinPEDeployFFUFiles/ExtendPartition-UEFI.txt +++ /dev/null @@ -1,10 +0,0 @@ -select disk 0 -select partition 3 -Assign letter="W" -shrink minimum=1000 -create partition primary -format quick fs=ntfs label="Recovery" -assign letter="R" -set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" -gpt attributes=0x8000000000000001 -exit diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 b/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 deleted file mode 100644 index d61f4e1..0000000 --- a/FFUDevelopment/WinPEDeployFFUFilesVM/ApplyFFU.ps1 +++ /dev/null @@ -1,622 +0,0 @@ -function Get-USBDrive(){ - $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Removable' -and $_.FileSystemType -eq 'NTFS'}).DriveLetter - if ($null -eq $USBDriveLetter){ - #Must be using a fixed USB drive - difficult to grab drive letter from win32_diskdrive. Assume user followed instructions and used Deploy as the friendly name for partition - $USBDriveLetter = (Get-Volume | Where-Object {$_.DriveType -eq 'Fixed' -and $_.FileSystemType -eq 'NTFS' -and $_.FileSystemLabel -eq 'Deploy'}).DriveLetter - #If we didn't get the drive letter, stop the script. - if ($null -eq $USBDriveLetter){ - WriteLog 'Cannot find USB drive letter - most likely using a fixed USB drive. Name the 2nd partition with the FFU files as Deploy so the script can grab the drive letter. Exiting' - Exit - } - - } - $USBDriveLetter = $USBDriveLetter + ":\" - return $USBDriveLetter -} - -# function Get-HardDrive(){ -# $DeviceID = (Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -ne 'Microsoft Virtual Disk'}).DeviceID -# return $DeviceID -# } -function Get-HardDrive(){ - $DiskDrive = Get-WmiObject -Class 'Win32_DiskDrive' | Where-Object {$_.MediaType -eq 'Fixed hard disk media' -and $_.Model -eq 'Microsoft Virtual Disk'} - $DeviceID = $DiskDrive.DeviceID - $BytesPerSector = $Diskdrive.BytesPerSector - - # Create a custom object to return both values - $result = New-Object PSObject -Property @{ - DeviceID = $DeviceID - BytesPerSector = $BytesPerSector - } - - return $result -} - -function WriteLog($LogText){ - Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -} - -function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){ - (Get-Content $DiskpartFile).Replace('disk 0', "disk $DiskID") | Set-Content -Path $DiskpartFile -} - -function Set-Computername($computername){ - [xml]$xml = Get-Content $UnattendFile - if($xml.unattend.settings.component.Count -ge 2){ - #Assumes that Computername is the first component element - $xml.unattend.settings.component[0].ComputerName = $computername - }else{ - $xml.unattend.settings.component.ComputerName = $computername - } - $xml.Save($UnattendFile) - return $computername -} - -function Invoke-Process { - [CmdletBinding(SupportsShouldProcess)] - param - ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string]$FilePath, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [string]$ArgumentList - ) - - $ErrorActionPreference = 'Stop' - - try { - $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" - $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" - - $startProcessParams = @{ - FilePath = $FilePath - ArgumentList = $ArgumentList - RedirectStandardError = $stdErrTempFile - RedirectStandardOutput = $stdOutTempFile - Wait = $true; - PassThru = $true; - NoNewWindow = $false; - } - if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { - $cmd = Start-Process @startProcessParams - $cmdOutput = Get-Content -Path $stdOutTempFile -Raw - $cmdError = Get-Content -Path $stdErrTempFile -Raw - if ($cmd.ExitCode -ne 0) { - if ($cmdError) { - throw $cmdError.Trim() - } - if ($cmdOutput) { - throw $cmdOutput.Trim() - } - } else { - if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { - WriteLog $cmdOutput - } - } - } - } catch { - #$PSCmdlet.ThrowTerminatingError($_) - WriteLog $_ - Write-Host 'Script failed - check scriptlog.txt on the USB drive for more info' - throw $_ - - } finally { - Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore - - } - -} - -# This function can be used in instances where battery level might matter (e.g. installing firmware for Surface). The problem is that WinPE doesn't have -# a driver for the battery installed, so you'll need to inject drivers, which can be tricky because just injecting the battery driver might not be enough, -# you might also need other drivers that the battery driver is dependent on. -# function Get-Battery(){ -# while (($BattLev = (Get-CimInstance win32_battery).EstimatedChargeRemaining) -lt "35") -# { -# WriteLog "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." -# Write-Host "Battery is currently at $BattLev`%. Waiting for 35`% to proceed..." -# Start-Sleep 60 -# } - -# WriteLog "Battery level is $BattLev `%, which is greater than 35'% applying FFU" -# Write-Host "Battery level is $BattLev `%, which is greater than 35'% applying FFU" -# } - -#Get USB Drive and create log file -$LogFileName = 'ScriptLog.txt' -$USBDrive = Get-USBDrive -New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null -$LogFile = $USBDrive + $LogFilename -$version = '2309.2' -WriteLog 'Begin Logging' -WriteLog "Script version: $version" - -#Find PhysicalDrive -$hardDrive = Get-HardDrive -# $PhysicalDeviceID = $hardDrive.DeviceID -$BytesPerSector = $hardDrive.BytesPerSector -# WriteLog "Physical DeviceID is $PhysicalDeviceID" -# WriteLog "Physical BytesPerSector is $BytesPerSector" -$PhysicalDeviceID = '\\.\PHYSICALDRIVE0' -WriteLog "Physical DeviceID is $PhysicalDeviceID" - - -#Parse DiskID Number -# $DiskID = $PhysicalDeviceID.substring($PhysicalDeviceID.length - 1,1) -# WriteLog "DiskID is $DiskID" -$DiskID = '0' -WriteLog "DiskID is $DiskID" - -#COMMENT THIS WHOLE BLOCK OUT ONCE FFUPROVIDER FIX IS IN -#Modify diskpart answer files if DiskID not 0 -# $UEFIFFUPartitions = 'x:\CreateUEFI-FFU-Partitions.txt' -$ExtendPartition = 'x:\ExtendPartition-UEFI.txt' - -If ($DiskID -ne '0'){ - WriteLog 'DiskID is not 0. Need to modify diskpart answer files' - # try { - # Set-DiskpartAnswerFiles $UEFIFFUPartitions $DiskID - # } - # catch { - # WriteLog "Modifying $UEFIFFUPartitions failed with error: $_" - # } - - try { - Set-DiskpartAnswerFiles $ExtendPartition $DiskID - } - catch { - WriteLog "Modifying $ExtendPartition failed with error: $_" - } -} - -#Find FFU Files -[array]$FFUFiles = @(Get-ChildItem -Path $USBDrive*.ffu) -$FFUCount = $FFUFiles.Count - -#If multiple FFUs found, ask which to install -If ($FFUCount -gt 1) { - WriteLog "Found $FFUCount FFU Files" - $array = @() - - for($i=0;$i -le $FFUCount -1;$i++){ - $Properties = [ordered]@{Number = $i + 1 ; FFUFile = $FFUFiles[$i].FullName} - $array += New-Object PSObject -Property $Properties - } - $array | Format-Table -AutoSize -Property Number, FFUFile - do { - try { - $var = $true - [int]$FFUSelected = Read-Host 'Enter the FFU number to install' - $FFUSelected = $FFUSelected -1 - } - - catch { - Write-Host 'Input was not in correct format. Please enter a valid FFU number' - $var = $false - } - } until (($FFUSelected -le $FFUCount -1) -and $var) - - $FFUFileToInstall = $array[$FFUSelected].FFUFile - WriteLog "$FFUFileToInstall was selected" -} -elseif ($FFUCount -eq 1) { - WriteLog "Found $FFUCount FFU File" - $FFUFileToInstall = $FFUFiles[0].FullName - WriteLog "$FFUFileToInstall will be installed" -} -else { - Writelog 'No FFU files found' - Write-Host 'No FFU files found' - Exit -} - -#FindAP -$APFolder = $USBDrive + "Autopilot\" -If (Test-Path -Path $APFolder){ - [array]$APFiles = @(Get-ChildItem -Path $APFolder*.json) - $APFilesCount = $APFiles.Count - if ($APFilesCount -ge 1){ - $autopilot = $true - } -} - - -#FindPPKG -$PPKGFolder = $USBDrive + "PPKG\" -if (Test-Path -Path $PPKGFolder){ - [array]$PPKGFiles = @(Get-ChildItem -Path $PPKGFolder*.ppkg) - $PPKGFilesCount = $PPKGFiles.Count - if ($PPKGFilesCount -ge 1){ - $PPKG = $true - } -} - -#FindUnattend -$UnattendFolder = $USBDrive + "unattend\" -$UnattendFilePath = $UnattendFolder + "unattend.xml" -$UnattendPrefixPath = $UnattendFolder + "prefixes.txt" -If (Test-Path -Path $UnattendFilePath){ - $UnattendFile = Get-ChildItem -Path $UnattendFilePath - If ($UnattendFile){ - $Unattend = $true - } -} -If (Test-Path -Path $UnattendPrefixPath){ - $UnattendPrefixFile = Get-ChildItem -Path $UnattendPrefixPath - If ($UnattendPrefixFile){ - $UnattendPrefix = $true - } -} - -#Ask for device name if unattend exists -if ($Unattend -and $UnattendPrefix){ - Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' - $UnattendPrefixes = @(Get-content $UnattendPrefixFile) - $UnattendPrefixCount = $UnattendPrefixes.Count - If ($UnattendPrefixCount -gt 1) { - WriteLog "Found $UnattendPrefixCount Prefixes" - $array = @() - for($i=0;$i -le $UnattendPrefixCount -1;$i++){ - $Properties = [ordered]@{Number = $i + 1 ; DeviceNamePrefix = $UnattendPrefixes[$i]} - $array += New-Object PSObject -Property $Properties - } - $array | Format-Table -AutoSize -Property Number, DeviceNamePrefix - do { - try { - $var = $true - [int]$PrefixSelected = Read-Host 'Enter the prefix number to use for the device name' - $PrefixSelected = $PrefixSelected -1 - } - catch { - Write-Host 'Input was not in correct format. Please enter a valid prefix number' - $var = $false - } - } until (($PrefixSelected -le $UnattendPrefixCount -1) -and $var) - $PrefixToUse = $array[$PrefixSelected].DeviceNamePrefix - WriteLog "$PrefixToUse was selected" - } - elseif ($UnattendPrefixCount -eq 1) { - WriteLog "Found $UnattendPrefixCount Prefix" - $PrefixToUse = $UnattendPrefixes[0] - WriteLog "Will use $PrefixToUse as device name prefix" - } - #Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace - $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() - #Combine prefix with serial - $computername = $PrefixToUse + $serial - #If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it - If ($computername.Length -gt 15){ - $computername = $computername.substring(0,15) - } - $computername = Set-Computername($computername) - Writelog "Computer name set to $computername" -} -elseif($Unattend){ - Writelog 'Unattend file found with no prefixes.txt, asking for name' - [string]$computername = Read-Host 'Enter device name' - Set-Computername($computername) - Writelog "Computer name set to $computername" -} -else { - WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' -} - -#If both AP and PPKG folder found with files, ask which to use. -If($autopilot -eq $true -and $PPKG -eq $true){ - WriteLog 'Both PPKG and Autopilot json files found' - Write-Host 'Both Autopilot JSON files and Provisioning packages were found.' - do { - try { - $var = $true - [int]$APorPPKG = Read-Host 'Enter 1 for Autopilot or 2 for Provisioning Package' - } - - catch { - Write-Host 'Incorrect value. Please enter 1 for Autopilot or 2 for Provisioning Package' - $var = $false - } - } until (($APorPPKG -gt 0 -and $APorPPKG -lt 3) -and $var) - If ($APorPPKG -eq 1){ - $PPKG = $false - } - else{ - $autopilot = $false - } -} - -#If multiple AP json files found, ask which to install -If ($APFilesCount -gt 1 -and $autopilot -eq $true) { - WriteLog "Found $APFilesCount Autopilot json Files" - $array = @() - - for($i=0;$i -le $APFilesCount -1;$i++){ - $Properties = [ordered]@{Number = $i + 1 ; APFile = $APFiles[$i].FullName; APFileName = $APFiles[$i].Name} - $array += New-Object PSObject -Property $Properties - } - $array | Format-Table -AutoSize -Property Number, APFileName - do { - try { - $var = $true - [int]$APFileSelected = Read-Host 'Enter the AP json file number to install' - $APFileSelected = $APFileSelected - 1 - } - - catch { - Write-Host 'Input was not in correct format. Please enter a valid AP json file number' - $var = $false - } - } until (($APFileSelected -le $APFilesCount -1) -and $var) - - $APFileToInstall = $array[$APFileSelected].APFile - $APFileName = $array[$APFileSelected].APFileName - WriteLog "$APFileToInstall was selected" -} -elseif ($APFilesCount -eq 1 -and $autopilot -eq $true) { - WriteLog "Found $APFilesCount AP File" - $APFileToInstall = $APFiles[0].FullName - $APFileName = $APFiles[0].Name - WriteLog "$APFileToInstall will be copied" -} -else { - Writelog 'No AP files found or AP was not selected' -} - -#If multiple PPKG files found, ask which to install -If ($PPKGFilesCount -gt 1 -and $PPKG -eq $true) { - WriteLog "Found $PPKGFilesCount PPKG Files" - $array = @() - - for($i=0;$i -le $PPKGFilesCount -1;$i++){ - $Properties = [ordered]@{Number = $i + 1 ; PPKGFile = $PPKGFiles[$i].FullName; PPKGFileName = $PPKGFiles[$i].Name} - $array += New-Object PSObject -Property $Properties - } - $array | Format-Table -AutoSize -Property Number, PPKGFileName - do { - try { - $var = $true - [int]$PPKGFileSelected = Read-Host 'Enter the PPKG file number to install' - $PPKGFileSelected = $PPKGFileSelected - 1 - } - - catch { - Write-Host 'Input was not in correct format. Please enter a valid PPKG file number' - $var = $false - } - } until (($PPKGFileSelected -le $PPKGFilesCount -1) -and $var) - - $PPKGFileToInstall = $array[$PPKGFileSelected].PPKGFile - WriteLog "$PPKGFileToInstall was selected" -} -elseif ($PPKGFilesCount -eq 1 -and $PPKG -eq $true) { - WriteLog "Found $PPKGFilesCount PPKG File" - $PPKGFileToInstall = $PPKGFiles[0].FullName - WriteLog "$PPKGFileToInstall will be used" -} -else { - Writelog 'No PPKG files found or PPKG not selected.' -} - -#Find Drivers -$Drivers = $USBDrive + "Drivers" -If (Test-Path -Path $Drivers) -{ - #Check if multiple driver folders found, if so, just select one folder to save time/space - $DriverFolders = Get-ChildItem -Path $Drivers - $DriverFoldersCount = $DriverFolders.count - If ($DriverFoldersCount -gt 1) - { - WriteLog "Found $DriverFoldersCount driver folders" - $array = @() - - for($i=0; $i -le $DriverFoldersCount -1; $i++){ - $Properties = [ordered]@{Number = $i + 1; Drivers = $DriverFolders[$i].FullName} - $array += New-Object PSObject -Property $Properties - } - $array | Format-Table -AutoSize -Property Number, Drivers - do { - try { - $var = $true - [int]$DriversSelected = Read-Host 'Enter the set of drivers to install' - $DriversSelected = $DriversSelected - 1 - } - - catch { - Write-Host 'Input was not in correct format. Please enter a valid driver folder number' - $var = $false - } - } until (($DriversSelected -le $DriverFoldersCount -1) -and $var) - - $Drivers = $array[$DriversSelected].Drivers - WriteLog "$Drivers was selected" - } - elseif ($DriverFoldersCount -eq 1) { - WriteLog "Found $DriverFoldersCount driver folder" - $Drivers = $DriverFolders.FullName - WriteLog "$Drivers will be installed" - } - else { - Writelog 'No driver folders found' - } -} - -#If you want to enable battery level checking, uncomment the line below as well as the Get-Battery function near the top of the script -#Get-Battery - -#Partition drive -Writelog 'Clean Disk' -#Start-Process -FilePath diskpart.exe -ArgumentList "/S $UEFIFFUPartitions" -Wait -ErrorAction Stop | Out-File $Logfile -Append -#Invoke-Process diskpart.exe "/S $UEFIFFUPartitions" -try { - $Disk = Get-Disk -Number $DiskID - $Disk | clear-disk -RemoveData -RemoveOEM -Confirm:$false -} -catch { - WriteLog 'Cleaning disk failed. Exiting' - throw $_ -} - -Writelog 'Cleaning Disk succeeded' - -#Apply FFU -WriteLog "Applying FFU to $PhysicalDeviceID" -WriteLog "Running command dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID" -#In order for Applying Image progress bar to show up, need to call dism directly. Might be a better way to handle, but must have progress bar show up on screen. -dism /apply-ffu /ImageFile:$FFUFileToInstall /ApplyDrive:$PhysicalDeviceID -if($LASTEXITCODE -eq 0){ - WriteLog 'Successfully applied FFU' -} -elseif($LASTEXITCODE -eq 1393){ - WriteLog "Failed to apply FFU - LastExitCode = $LastExitCode" - WriteLog "This is likely due to a mismatched LogicalSectorByteSize" - WriteLog "BytesPerSector value from Win32_Diskdrive is $BytesPerSector" - if ($BytesPerSector -eq 4096){ - WriteLog "The FFU build process by default uses a 512 LogicalSectorByteSize. Rebuild the FFU by adding -LogicalSectorByteSize 4096 to the command line" - } - elseif($BytesPerSector -eq 512){ - WriteLog "This FFU was likely built with a LogicalSectorByteSize of 4096. Rebuild the FFU by adding -LogicalSectorByteSize 512 to the command line" - } - #Copy DISM log to USBDrive - invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" - exit -} -else{ - Writelog "Failed to apply FFU - LastExitCode = $LASTEXITCODE also check dism.log on the USB drive for more info" - #Copy DISM log to USBDrive - invoke-process xcopy.exe "X:\Windows\logs\dism\dism.log $USBDrive /Y" - exit -} - -#Remove recovery partition - this is needed in order to extend the Windows partition so it uses the full disk size. If dism /optimize-ffu worked, this wouldn't be needed -# $disk = get-disk -Number $DiskID -# $RecoveryPartition = $disk | get-partition | Where-Object {$_.type -eq 'Recovery'} -# if ($RecoveryPartition){ -# $RecoveryPartitionNumber = $RecoveryPartition.PartitionNumber -# if ($RecoveryPartitionNumber -eq 4){ -# try { -# WriteLog 'Removing recovery partition' -# Remove-partition -DiskNumber $DiskID -PartitionNumber $RecoveryPartitionNumber -Confirm:$false -# } -# catch { -# WriteLog 'Error removing recovery partition, exiting' -# throw $_ -# } -# } -# else{ -# WriteLog 'Recovery partition not partition 4. Script will exit. Please create the FFU with the recovery partition as the last partition. This is the default and recommended way.' -# exit -# } -# } - -#COMMENT THIS WHOLE BLOCK OUT AFTER FFUPROVIDER FIX IS IN -# Extend Windows partition and create recovery partition -Writelog 'Extending Windows partition' -Invoke-Process diskpart.exe "/S $ExtendPartition" -if($LASTEXITCODE -eq 0){ - WriteLog 'Successfully extended Windows partition and created recovery partition' -} -else{ - Writelog "Failed to extend Windows partition and/or create recovery partition - LastExitCode = $LASTEXITCODE" -} - -#UNCOMMENT THIS AFTER FFUPROVIDER FIX IS IN -#Set W: drive letter to Windows partition -#Get-Disk | Where-Object Number -eq $DiskID | Get-Partition | Where-Object PartitionNumber -eq 3 | Set-Partition -NewDriveLetter W - -#Copy modified WinRE if folder exists, else copy inbox WinRE -$WinRE = $USBDrive + "WinRE\winre.wim" -If (Test-Path -Path $WinRE) -{ - WriteLog 'Copying modified WinRE to Recovery directory' - Invoke-Process xcopy.exe "/h $WinRE R:\Recovery\WindowsRE\ /Y" - WriteLog 'Copying WinRE to Recovery directory succeeded' - WriteLog 'Registering location of recovery tools' - Invoke-Process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" - WriteLog 'Registering location of recovery tools succeeded' -} -# else -# { -# WriteLog 'Copying default WinRE to Recovery directory' -# Invoke-Process xcopy.exe "/h W:\Windows\System32\Recovery\Winre.wim R:\Recovery\WindowsRE\ /Y" -# WriteLog 'Copying WinRE to Recovery directory succeeded' -# WriteLog 'Registering location of recovery tools' -# Invoke-process W:\Windows\System32\Reagentc.exe "/Setreimage /Path R:\Recovery\WindowsRE /Target W:\Windows" -# WriteLog 'Registering location of recovery tools succeeded' -# } - -#Autopilot JSON -If ($APFileToInstall){ - WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot" - Invoke-process xcopy.exe "$APFileToInstall W:\Windows\provisioning\autopilot\" - WriteLog "Copying $APFileToInstall to W:\windows\provisioning\autopilot succeeded" - # Rename file in W:\Windows\Provisioning\Autopilot to AutoPilotConfigurationFile.json - try { - Rename-Item -Path "W:\Windows\Provisioning\Autopilot\$APFileName" -NewName 'W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json' - WriteLog "Renamed W:\Windows\Provisioning\Autopilot\$APFilename to W:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json" - } - - catch{ - Writelog "Copying $APFileToInstall to W:\windows\provisioning\autopilot failed with error: $_" - throw $_ - } -} -#Apply PPKG -If ($PPKGFileToInstall){ - try { - #Make sure to delete any existing PPKG on the USB drive - Get-Childitem -Path $USBDrive\*.ppkg | ForEach-Object { - Remove-item -Path $_.FullName - } - WriteLog "Copying $PPKGFileToInstall to $USBDrive" - Invoke-process xcopy.exe "$PPKGFileToInstall $USBDrive" - WriteLog "Copying $PPKGFileToInstall to $USBDrive succeeded" - } - - catch{ - Writelog "Copying $PPKGFileToInstall to $USBDrive failed with error: $_" - throw $_ - } -} -#Set DeviceName -If ($computername){ - try{ - $PantherDir = 'w:\windows\panther' - If (Test-Path -Path $PantherDir){ - Writelog "Copying $UnattendFile to $PantherDir" - Invoke-process xcopy "$UnattendFile $PantherDir /Y" - WriteLog "Copying $UnattendFile to $PantherDir succeeded" - } - else{ - Writelog "$PantherDir doesn't exist, creating it" - New-Item -Path $PantherDir -ItemType Directory -Force - Writelog "Copying $UnattendFile to $PantherDir" - Invoke-Process xcopy.exe "$UnattendFile $PantherDir" - WriteLog "Copying $UnattendFile to $PantherDir succeeded" - } - } - catch{ - WriteLog "Copying Unattend.xml to name device failed" - throw $_ - } -} - -#Add Drivers -#Some drivers can sometimes fail to copy and dism ends up with a non-zero error code. Invoke-process will throw and terminate in these instances. -If (Test-Path -Path $Drivers) -{ - WriteLog 'Copying drivers' - Write-Warning 'Copying Drivers - dism will pop a window with no progress. This can take a few minutes to complete. This is done so drivers are logged to the scriptlog.txt file. Please be patient.' - Invoke-process dism.exe "/image:W:\ /Add-Driver /Driver:""$Drivers"" /Recurse" - WriteLog 'Copying drivers succeeded' -} - -#Copy DISM log to USBDrive -WriteLog "Copying dism log to $USBDrive" -invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y" -WriteLog "Copying dism log to $USBDrive succeeded" - - - - diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt b/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt deleted file mode 100644 index dbfe4b9..0000000 --- a/FFUDevelopment/WinPEDeployFFUFilesVM/ExtendPartition-UEFI.txt +++ /dev/null @@ -1,10 +0,0 @@ -select disk 0 -select partition 3 -Assign letter="W" -shrink minimum=1000 -create partition primary -format quick fs=ntfs label="Recovery" -assign letter="R" -set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" -gpt attributes=0x8000000000000001 -exit diff --git a/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd b/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd deleted file mode 100644 index 12519ba..0000000 --- a/FFUDevelopment/WinPEDeployFFUFilesVM/Windows/System32/startnet.cmd +++ /dev/null @@ -1,5 +0,0 @@ -wpeinit -powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c -powershell -Noprofile -ExecutionPolicy Bypass -File x:\ApplyFFU.ps1 -exit - From 39a919badabf774251b4ae232ecc6c2f69318251 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:31:53 -0700 Subject: [PATCH 40/43] Changed AppList.json file name to AppList_Sample.json to not force the sample apps to install automatically. --- FFUDevelopment/Apps/{AppList.json => AppList_Sample.json} | 5 ----- 1 file changed, 5 deletions(-) rename FFUDevelopment/Apps/{AppList.json => AppList_Sample.json} (71%) diff --git a/FFUDevelopment/Apps/AppList.json b/FFUDevelopment/Apps/AppList_Sample.json similarity index 71% rename from FFUDevelopment/Apps/AppList.json rename to FFUDevelopment/Apps/AppList_Sample.json index a33c2b9..b10f813 100644 --- a/FFUDevelopment/Apps/AppList.json +++ b/FFUDevelopment/Apps/AppList_Sample.json @@ -1,10 +1,5 @@ { "apps": [ - { - "name": "7-Zip", - "id": "7zip.7zip", - "source": "winget" - }, { "name": "Company Portal", "id": "9WZDNCRFJ3PZ", From 50c61dd3281540f03129992566546400e6d6820b Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:34:33 -0700 Subject: [PATCH 41/43] Added AppList_InboxAppsSample.json --- .../Apps/AppList_InboxAppsSample.json | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 FFUDevelopment/Apps/AppList_InboxAppsSample.json diff --git a/FFUDevelopment/Apps/AppList_InboxAppsSample.json b/FFUDevelopment/Apps/AppList_InboxAppsSample.json new file mode 100644 index 0000000..ddc2240 --- /dev/null +++ b/FFUDevelopment/Apps/AppList_InboxAppsSample.json @@ -0,0 +1,209 @@ +{ + "apps": [ + { + "name": "Windows Terminal", + "id": "9N0DX20HK701", + "source": "msstore" + }, + { + "name": "Cross Device Experience Host", + "id": "9NTXGKQ8P7N0", + "source": "msstore" + }, + { + "name": "Movies & TV", + "id": "9WZDNCRFJ3P2", + "source": "msstore" + }, + { + "name": "Microsoft Photos", + "id": "9WZDNCRFJBH4", + "source": "msstore" + }, + { + "name": "Mail and Calendar", + "id": "9WZDNCRFHVQM", + "source": "msstore" + }, + { + "name": "Microsoft Sticky Notes", + "id": "9NBLGGH4QGHW", + "source": "msstore" + }, + { + "name": "Power Automate", + "id": "9NFTCH6J7FHV", + "source": "msstore" + }, + { + "name": "Snipping Tool", + "id": "9MZ95KL8MR0L", + "source": "msstore" + }, + { + "name": "Phone Link", + "id": "9NMPJ99VJBWV", + "source": "msstore" + }, + { + "name": "Microsoft 365 (Office Hub)", + "id": "9WZDNCRFJBH4", + "source": "msstore" + }, + { + "name": "App Installer", + "id": "9NBLGGH4NNS1", + "source": "msstore" + }, + { + "name": "Microsoft Clipchamp", + "id": "9P1J8S7CCWWT", + "source": "msstore" + }, + { + "name": "Webp Image Extensions", + "id": "9PG2DK419DRG", + "source": "msstore" + }, + { + "name": "Windows Web Experience Pack", + "id": "9MSSGKG348SP", + "source": "msstore" + }, + { + "name": "Xbox", + "id": "9MV0B5HZVK9Z", + "source": "msstore" + }, + { + "name": "Paint", + "id": "9PCFS5B6T72H", + "source": "msstore" + }, + { + "name": "Windows Camera", + "id": "9WZDNCRFJBBG", + "source": "msstore" + }, + { + "name": "Windows Notepad", + "id": "9MSMLRH6LZF3", + "source": "msstore" + }, + { + "name": "Windows Sound Recorder", + "id": "9WZDNCRFHWKN", + "source": "msstore" + }, + { + "name": "Windows Calculator", + "id": "9WZDNCRFHVN5", + "source": "msstore" + }, + { + "name": "Feedback Hub", + "id": "9NBLGGH4R32N", + "source": "msstore" + }, + { + "name": "Xbox Identity Provider", + "id": "9WZDNCRD1HKW", + "source": "msstore" + }, + { + "name": "Windows Media Player", + "id": "9WZDNCRFJ3PT", + "source": "msstore" + }, + { + "name": "MSN Weather", + "id": "9WZDNCRFJ3Q2", + "source": "msstore" + }, + { + "name": "Game Bar", + "id": "9NZKPSTSNW4P", + "source": "msstore" + }, + { + "name": "Web Media Extensions", + "id": "9N5TDP8VCMHS", + "source": "msstore" + }, + { + "name": "Get Help", + "id": "9PKDZBMV1H3T", + "source": "msstore" + }, + { + "name": "Raw Image Extension", + "id": "9NCTDW2W1BH8", + "source": "msstore" + }, + { + "name": "Store Experience Host", + "id": "9NBLGGH4LS1F", + "source": "msstore" + }, + { + "name": "Windows Maps", + "id": "9WZDNCRDTBVB", + "source": "msstore" + }, + { + "name": "Windows Clock", + "id": "9WZDNCRFJ3PR", + "source": "msstore" + }, + { + "name": "Microsoft To Do", + "id": "9NBLGGH5R558", + "source": "msstore" + }, + { + "name": "Cortana", + "id": "9NFFX4SZZ23L", + "source": "msstore" + }, + { + "name": "Quick Assist", + "id": "9P7BP5VNWKX5", + "source": "msstore" + }, + { + "name": "HEIF Image Extensions", + "id": "9PMMSR1CGPWG", + "source": "msstore" + }, + { + "name": "VP9 Video Extensions", + "id": "9N4D0MSMP0PT", + "source": "msstore" + }, + { + "name": "Xbox Live in-game experience", + "id": "9NKNC0LD5NN6", + "source": "msstore" + }, + { + "name": "Xbox Game Speech Window", + "id": "9P086NHDNB9W", + "source": "msstore" + }, + { + "name": "Microsoft News", + "id": "9WZDNCRFHVFW", + "source": "msstore" + }, + { + "name": "Microsoft Store", + "id": "9WZDNCRFJBMP", + "source": "msstore" + }, + { + "name": "Microsoft Tips", + "id": "9WZDNCRDTBJJ", + "source": "msstore" + } + ] +} \ No newline at end of file From 0a9de96d03f0ad82a27db1847cf0d6be401fe571 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:55:24 -0700 Subject: [PATCH 42/43] Potential path fix for downloading defender defs for ARM64 --- ChangeLog.md | 17 +++++++++++++++++ FFUDevelopment/BuildFFUVM.ps1 | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5340847..844bc2e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,22 @@ # Change Log +## **2407.1** + +This is another major release that includes: + +* Initial ARM64 support +* Winget support + +### ARM64 Support + +To support the newly released Copilot+ PCs, we now support the creation and deployment of FFUs created with ARM64 media. There are some caveats to this: + +* The -WindowsArch parameter must be set to ARM64 (by default this parameter is set to x64) +* If you do not pass -ISOPath with a path to the ARM64 ISO, it will download an ARM64 ESD file from the Media Creation Tool (which is about 7-8 months old now). ARM64 ISOs are available via VLSC, but are not available via Visual Studio Downloads. +* The host machine you're building the FFU from must be ARM64 +* Office/M365 apps don't currently support installing the ARM64 native bits from an offline system. If you pass `-InstallOffice $true` the script will change the value to false. You can install office after the fact when connected to the internet. I'm investigating this behavior and will issue a fix if/when this gets resolved. I still don't recommend building the FFU VM on the internet. +* The ARM64 native bits from the Microsoft + ## **2406.1** This is a major release that includes the ability to download drivers from the 4 major OEMs (Microsoft, Dell, HP, Lenovo) by simply passing the -Make and -Model parameters to the command line. diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 4c53eed..7acaa69 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3419,7 +3419,7 @@ if ($InstallApps) { $DefenderDefURL = 'https://go.microsoft.com/fwlink/?LinkID=121721&arch=x64' } if ($WindowsArch -eq 'ARM64') { - $DefenderDefURL = 'https://go.microsoft.com/fwlink/?LinkID=121721&arch=arm' + $DefenderDefURL = 'https://go.microsoft.com/fwlink/?LinkID=121721&arch=arm64' } try { WriteLog "Defender definitions URL is $DefenderDefURL" From 2e7ab9a052efae858af5a73b3b1ad3be55c18136 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:46:53 -0700 Subject: [PATCH 43/43] change log updates for 2407.1 --- ChangeLog.md | 41 ++++++++++++++++++++++++++++-- image/ChangeLog/1721678632154.png | Bin 0 -> 60574 bytes image/ChangeLog/1721679421727.png | Bin 0 -> 28889 bytes image/ChangeLog/1721681140638.png | Bin 0 -> 124959 bytes 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 image/ChangeLog/1721678632154.png create mode 100644 image/ChangeLog/1721679421727.png create mode 100644 image/ChangeLog/1721681140638.png diff --git a/ChangeLog.md b/ChangeLog.md index 844bc2e..9e3ae9f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,10 +12,47 @@ This is another major release that includes: To support the newly released Copilot+ PCs, we now support the creation and deployment of FFUs created with ARM64 media. There are some caveats to this: * The -WindowsArch parameter must be set to ARM64 (by default this parameter is set to x64) -* If you do not pass -ISOPath with a path to the ARM64 ISO, it will download an ARM64 ESD file from the Media Creation Tool (which is about 7-8 months old now). ARM64 ISOs are available via VLSC, but are not available via Visual Studio Downloads. +* If you do not pass -ISOPath with a path to the ARM64 ISO, it will download an ARM64 ESD file from the Media Creation Tool (which is about 7-8 months old now). ARM64 ISOs are available via VLSC, but are not available via Visual Studio Downloads (Yet - unknown if they will ever be made available). * The host machine you're building the FFU from must be ARM64 * Office/M365 apps don't currently support installing the ARM64 native bits from an offline system. If you pass `-InstallOffice $true` the script will change the value to false. You can install office after the fact when connected to the internet. I'm investigating this behavior and will issue a fix if/when this gets resolved. I still don't recommend building the FFU VM on the internet. -* The ARM64 native bits from the Microsoft +* The [Defender Updates Site](https://www.microsoft.com/en-us/wdsi/defenderupdates) provides download links for Defender definitions. The ARM link doesn't work for ARM64 and mpam-fe.exe fails to install. However there might be an undocumented ARM64 URL that may work. I've included it, but haven't tested it as I'm writing these notes. So we'll see if that works out. +* Drivers - I know Surface Laptop 7 and Pro + +In all, testing has gone very well. + +### Winget Support + +Big thanks to [Zehadi Alam](https://github.com/zehadialam) for his contributions to get this added to the project. You can now add any application in the msstore or winget source via the Winget command line utility. In the 1.8 Winget release the ability to download apps from the msstore source was added, which means being able to download apps like the Company Portal. For those of you that have been asking for Company Portal to be inbox in Windows, this is the next best thing. The script will check if Winget 1.8 is installed and if not, it'll install it. + +The way this works is if `-InstallApps $true` and the FFUDevelopment\Apps\AppList.json file exists, whatever apps defined in that json file will be downloaded via Winget and will be installed in the FFU VM prior to capture. We've included two files: AppList_InboxAppsSample.json and AppList_Sample.json. The AppList_InboxAppsSample.json contains all of the apps that are installed in Windows by default and are searchable via `winget search AppID` . Some of these apps do not download and we're investigating why they come up via search, but fail to download. The AppList_Sample.json has Company Portal and New Teams. + +![1721678632154](image/ChangeLog/1721678632154.png) + +In sticking with the idea of having the most up to date Windows build, inbox store/UWP apps are notoriously out of date and use a lot of bandwidth. By updating all of the UWP apps, bandwidth reductions of ~70% can be achieved. + +| | Total Data usage before updating store apps | Total Data usage after updating store apps | Total Data usage after updating Windows Update | +| --------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------ | ---------------------------------------------- | +| July 2024 Windows 11 23H2 Stock ISO Captured as FFU (7.5GB FFU) | 261MB | 1.82GB | 2.09GB | +| July 2024 Windows 11 23H2 Updated FFU (10.5GB) | 13MB | 558MB | 646MB | + +Updated means latest .NET, Defender (definition and platform updates), Edge, OneDrive, and all updates available via Winget for Store Apps have been provisioned in the FFU. The numbers in the table are cumulative, meaning the FFU was laid down, store apps were updated via running Get Apps from the Microsoft Store app and data usage was gathered from Settings, then Windows Update was manually kicked off via Settings and data usage was gathered. + +In order to get apps to help build your AppList.json file, just run `winget search "AppName"` + +![1721679421727](image/ChangeLog/1721679421727.png) + +In this example we see that Firefox is published to both the msstore and winget sources. It's up to you which one you'd like to pick (I assume the msstore and the 128 version from the winget source are both the same version, but that may not be the case). You'll want to use the Name, ID, and Source values to help create your AppList.json file. + +Other improvements + +* [mhaley](https://github.com/mhaley) made their first contribution to [assign the drive letter to the recovery partition when copying in a custom WinRE.wim](https://github.com/rbalsleyMSFT/FFU/pull/35) +* [MKellyCBSD](https://github.com/MKellyCBSD) submitted a PR for a stand-alone USBImagingToolCreator.ps1 script which will create USB drives separate from the main BuildFUVM.ps1 script. This is helpful if you have technicans that need to build USB drives, or would like to make concurrent USB drives at the same time instead of one at a time. [His PR has all the details.](https://github.com/rbalsleyMSFT/FFU/pull/36) +* The WinPE_FFU_Deploy.iso will now work on VMs. This made ARM64 testing a lot easier :) If you're looking to test your FFU on a VM, you'll want to build a new VHDX and add your FFU to it and boot from the WinPE_FFU_Deploy.iso. Make sure to eject the VHDX before adding/booting the new VM. When attaching the new VHDX with your FFU on it, make sure it's not the first SCSI device (it should be 1 or 2, most likely 2 as 0 should be the hard drive you want to install Windows to, and 1 will be the DVD drive). By default the WinPE_FFU_Deploy.iso file is removed after the script completes. Make sure to set `-CreateDeploymentMedia $true` and `-CleanupDeployISO $false` so the ISO remains in the FFUDevelopment folder after the script completes. + + The below screenshot should help in understanding what the SCSI config should look like. + + ![1721681140638](image/ChangeLog/1721681140638.png) +* Cleaned up some old commented code from the ApplyFFU.ps1 file and other files. ## **2406.1** diff --git a/image/ChangeLog/1721678632154.png b/image/ChangeLog/1721678632154.png new file mode 100644 index 0000000000000000000000000000000000000000..a6434392174e5cb16e7829c3049c997959e8cf22 GIT binary patch literal 60574 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

Y&Yl<17{0R5it8pL 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 FzX5Ui(sBR* literal 0 HcmV?d00001 diff --git a/image/ChangeLog/1721679421727.png b/image/ChangeLog/1721679421727.png new file mode 100644 index 0000000000000000000000000000000000000000..d49e2b5cfa0dee159f343447af103668e6494aee GIT binary patch literal 28889 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%0AA5L&1+t#b^r{+_+`uBX(joSTFi}yV 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 Lsmhj0o4)^FrnUy& literal 0 HcmV?d00001