mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1aac7ba9d | |||
| 2e7ab9a052 | |||
| 0a9de96d03 | |||
| 50c61dd328 | |||
| 39a919bada | |||
| 5600b2fbbd | |||
| 1d26781dc1 | |||
| f29e3c4349 | |||
| bd8d0efd66 | |||
| 5616082275 | |||
| 8100df3d24 | |||
| a5c38fd09b | |||
| 88ef8f70a1 | |||
| 74fd71161b | |||
| 8aa79dd134 | |||
| e4e499e796 | |||
| f6c3c0b6c3 | |||
| 60ac2e4af0 | |||
| ddf9c1f986 | |||
| ab58b27a1d | |||
| 191c30dd65 | |||
| 1a444d8e0f | |||
| f7f52903a4 | |||
| a9afba9185 | |||
| ecf3794f92 | |||
| c0bcfd8bf2 | |||
| 98babb3ad2 | |||
| ab0b7f67ec | |||
| 6d85e3ef62 | |||
| a91a417a08 | |||
| 67cc8c1225 | |||
| 3f0377fbf9 | |||
| 325413de13 | |||
| 174c16ecb6 | |||
| 146c1601bd | |||
| 8c897e93fe | |||
| 2423814cc2 | |||
| c39c30c970 | |||
| 95a4664b26 | |||
| 0e6d65bf2f | |||
| 0010c8ad81 | |||
| d16acce0ab | |||
| dd20eceb55 | |||
| c30aa90e8b | |||
| bd4e3a1913 | |||
| 7a0dd3435c | |||
| df33e89e37 | |||
| 4af808c939 | |||
| 205b58aaa7 | |||
| 1729eaddd6 | |||
| cafff0b484 | |||
| 5c77b171f1 | |||
| 36f5350f12 | |||
| 45a2c0c29d | |||
| 6cfe41f963 | |||
| 3d13774ee4 |
@@ -1,5 +1,59 @@
|
||||
# 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 (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 [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.
|
||||
|
||||

|
||||
|
||||
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"`
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
* Cleaned up some old commented code from the ApplyFFU.ps1 file and other files.
|
||||
|
||||
## **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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"name": "Company Portal",
|
||||
"id": "9WZDNCRFJ3PZ",
|
||||
"source": "msstore"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Teams",
|
||||
"id": "Microsoft.Teams",
|
||||
"source": "winget"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -6,9 +7,59 @@ 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
|
||||
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!\" (
|
||||
if /i not "%%~xF"==".xml" (
|
||||
if /i not "%%~xF"==".yaml" (
|
||||
set "mainpackage=%%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!" (
|
||||
for %%G in ("!dependenciesfolder!\*") 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 (
|
||||
set "dism_command=!dism_command! /SkipLicense"
|
||||
)
|
||||
set "dism_command=!dism_command! /Region:All"
|
||||
echo !dism_command!
|
||||
!dism_command!
|
||||
)
|
||||
)
|
||||
: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
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
<settings pass="auditUser">
|
||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="arm64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<RunAsynchronous>
|
||||
<RunAsynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<Path>d:\InstallAppsandSysprep.cmd</Path>
|
||||
</RunAsynchronousCommand>
|
||||
</RunAsynchronous>
|
||||
</component>
|
||||
</settings>
|
||||
<settings pass="oobeSystem">
|
||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="arm64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Reseal>
|
||||
<Mode>Audit</Mode>
|
||||
</Reseal>
|
||||
</component>
|
||||
</settings>
|
||||
<cpi:offlineImage cpi:source="wim:c:/wimtoffu/win11_22h2_feb2023_consumer.wim#Windows 11 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
||||
</unattend>
|
||||
+519
-63
@@ -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
|
||||
@@ -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" }
|
||||
@@ -1533,7 +1533,7 @@ function Get-WindowsESD {
|
||||
[int]$WindowsRelease,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet('x86', 'x64')]
|
||||
[ValidateSet('x86', 'x64', 'ARM64')]
|
||||
[string]$WindowsArch,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
@@ -1657,6 +1657,298 @@ function Get-Office {
|
||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $content
|
||||
}
|
||||
}
|
||||
|
||||
function Install-WinGet {
|
||||
param (
|
||||
[string]$Architecture
|
||||
)
|
||||
$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 "WinGet installation complete."
|
||||
}
|
||||
|
||||
function Confirm-WinGetInstallation {
|
||||
WriteLog 'Checking if WinGet is installed...'
|
||||
$wingetPath = "$env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe"
|
||||
$minVersion = [version]"1.8.1911"
|
||||
if (-not (Test-Path -Path $wingetPath -PathType Leaf)) {
|
||||
WriteLog "WinGet is not installed. Downloading WinGet..."
|
||||
Install-WinGet -Architecture $WindowsArch
|
||||
return
|
||||
}
|
||||
if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) {
|
||||
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
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function Add-Win32SilentInstallCommand {
|
||||
param (
|
||||
[string]$AppFolder,
|
||||
[string]$AppFolderPath
|
||||
)
|
||||
$appName = $AppFolder
|
||||
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Include "*.exe", "*.msi" -File -ErrorAction Stop
|
||||
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.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 $false
|
||||
}
|
||||
$installer = Split-Path -Path $installerPath -Leaf
|
||||
if ($installerPath.Extension -eq ".exe") {
|
||||
$silentInstallCommand = "`"D:\win32\$appFolder\$installer`" $silentInstallSwitch"
|
||||
}
|
||||
elseif ($installerPath.Extension -eq ".msi") {
|
||||
$silentInstallCommand = "msiexec /i `"D:\win32\$appFolder\$installer`" $silentInstallSwitch"
|
||||
}
|
||||
$cmdFile = "$AppsPath\InstallAppsandSysprep.cmd"
|
||||
$cmdContent = Get-Content -Path $cmdFile
|
||||
$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 {
|
||||
$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,
|
||||
[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."
|
||||
}
|
||||
$appFolderPath = Join-Path -Path "$AppsPath\Win32" -ChildPath $WinGetAppName
|
||||
WriteLog "Creating $appFolderPath"
|
||||
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
|
||||
WriteLog "Downloading $WinGetAppName to $appFolderPath"
|
||||
$downloadParams = @(
|
||||
"download",
|
||||
"--id", "$WinGetAppId",
|
||||
"--exact",
|
||||
"--download-directory", "$appFolderPath",
|
||||
"--accept-package-agreements",
|
||||
"--accept-source-agreements",
|
||||
"--source", "winget",
|
||||
"--scope", "machine",
|
||||
"--architecture", "$WindowsArch"
|
||||
)
|
||||
WriteLog "winget command: winget.exe $downloadParams"
|
||||
$wingetDownloadResult = & winget.exe @downloadParams | Out-String
|
||||
if ($wingetDownloadResult -match "No applicable installer found") {
|
||||
WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
|
||||
$downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" }
|
||||
$wingetDownloadResult = & winget.exe @downloadParams | Out-String
|
||||
if ($wingetDownloadResult -match "Installer downloaded") {
|
||||
WriteLog "Downloaded $WinGetAppName without specifying architecture."
|
||||
}
|
||||
}
|
||||
if ($wingetDownloadResult -notmatch "Installer downloaded") {
|
||||
WriteLog "No installer found for $WinGetAppName. Skipping download."
|
||||
Remove-Item -Path $appFolderPath -Recurse -Force
|
||||
}
|
||||
WriteLog "$WinGetAppName downloaded to $appFolderPath"
|
||||
$installerPath = Get-ChildItem -Path "$appFolderPath\*" -Exclude "*.yaml", "*.xml" -File -ErrorAction Stop
|
||||
$uwpExtensions = @(".appx", ".appxbundle", ".msix", ".msixbundle")
|
||||
if ($uwpExtensions -contains $installerPath.Extension) {
|
||||
$NewAppPath = "$AppsPath\MSStore\$WinGetAppName"
|
||||
Writelog "$WinGetAppName is a UWP app. Moving to $NewAppPath"
|
||||
WriteLog "Creating $NewAppPath"
|
||||
New-Item -Path "$AppsPath\MSStore\$WinGetAppName" -ItemType Directory -Force | Out-Null
|
||||
WriteLog "Moving $WinGetAppName to $NewAppPath"
|
||||
Move-Item -Path "$appFolderPath\*" -Destination "$AppsPath\MSStore\$WinGetAppName" -Force
|
||||
WriteLog "Removing $appFolderPath"
|
||||
Remove-Item -Path $appFolderPath -Force
|
||||
WriteLog "$WinGetAppName moved to $NewAppPath"
|
||||
Set-InstallStoreAppsFlag
|
||||
}
|
||||
else {
|
||||
Add-Win32SilentInstallCommand -AppFolder $WinGetAppName -AppFolderPath $appFolderPath
|
||||
}
|
||||
}
|
||||
|
||||
function Get-StoreApp {
|
||||
param (
|
||||
[string]$StoreAppName,
|
||||
[string]$StoreAppId
|
||||
)
|
||||
$wingetSearchResult = & winget.exe search "$StoreAppId" --accept-source-agreements --source msstore
|
||||
if ($wingetSearchResult -contains "No package found matching input criteria.") {
|
||||
WriteLog "$StoreAppName not found in WinGet repository. Skipping download."
|
||||
return
|
||||
}
|
||||
WriteLog "Checking if $StoreAppName is a win32 app..."
|
||||
$appIsWin32 = $StoreAppId.StartsWith("XP")
|
||||
if ($appIsWin32) {
|
||||
WriteLog "$StoreAppName is a win32 app. Adding to $AppsPath\win32 folder"
|
||||
$appFolderPath = Join-Path -Path "$AppsPath\win32" -ChildPath $StoreAppName
|
||||
}
|
||||
else {
|
||||
WriteLog "$StoreAppName is not a win32 app."
|
||||
$appFolderPath = Join-Path -Path "$AppsPath\MSStore" -ChildPath $StoreAppName
|
||||
}
|
||||
New-Item -Path $appFolderPath -ItemType Directory -Force | Out-Null
|
||||
WriteLog "Downloading $StoreAppName for $WindowsArch architecture..."
|
||||
$downloadParams = @(
|
||||
"download", "$StoreAppId",
|
||||
"--download-directory", "$appFolderPath",
|
||||
"--accept-package-agreements",
|
||||
"--accept-source-agreements",
|
||||
"--source", "msstore",
|
||||
"--scope", "machine",
|
||||
"--architecture", "$WindowsArch"
|
||||
)
|
||||
WriteLog 'MSStore app downloads require authentication with an Entra ID account. You may be prompted twice for credentials, once for the app and another for the license file.'
|
||||
WriteLog "Attempting to download $StoreAppName and dependencies for $WindowsArch architecture..."
|
||||
$wingetDownloadResult = & winget.exe @downloadParams | Out-String
|
||||
# For some apps, specifying the architecture leads to no results found for the app. In those cases, the command will be run without the architecture parameter.
|
||||
if ($wingetDownloadResult -match "No applicable installer found") {
|
||||
WriteLog "No installer found for $WindowsArch architecture. Attempting to download without specifying architecture..."
|
||||
$downloadParams = $downloadParams | Where-Object { $_ -notmatch "--architecture" -and $_ -notmatch "$WindowsArch" }
|
||||
$wingetDownloadResult = & winget.exe @downloadParams | Out-String
|
||||
if ($wingetDownloadResult -match "Microsoft Store package download completed") {
|
||||
WriteLog "Downloaded $StoreAppName without specifying architecture."
|
||||
}
|
||||
}
|
||||
if ($wingetDownloadResult -notmatch "Installer downloaded|Microsoft Store package download completed") {
|
||||
WriteLog "Download not supported for $StoreAppName. Skipping download."
|
||||
Remove-Item -Path $appFolderPath -Recurse -Force
|
||||
return
|
||||
}
|
||||
if ($appIsWin32) {
|
||||
Add-Win32SilentInstallCommand -AppFolder $StoreAppName -AppFolderPath $appFolderPath
|
||||
}
|
||||
Set-InstallStoreAppsFlag
|
||||
# If $WindowsArch -eq 'ARM64', remove all dependency files that are not ARM64
|
||||
if ($WindowsArch -eq 'ARM64') {
|
||||
WriteLog 'Windows architecture is ARM64. Removing dependencies that are not ARM64.'
|
||||
$dependencies = Get-ChildItem -Path "$appFolderPath\Dependencies" -ErrorAction SilentlyContinue
|
||||
if ($dependencies) {
|
||||
foreach ($dependency in $dependencies) {
|
||||
if ($dependency.Name -notmatch 'ARM64') {
|
||||
WriteLog "Removing dependency file $($dependency.FullName)"
|
||||
Remove-Item -Path $dependency.FullName -Recurse -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WriteLog "$StoreAppName has completed downloading. Identifying the latest version of $StoreAppName."
|
||||
$packages = Get-ChildItem -Path "$appFolderPath\*" -Exclude "Dependencies\*", "*.xml", "*.yaml" -File -ErrorAction Stop
|
||||
# WinGet downloads multiple versions of certain store apps. The latest version of the package will be determined based on the date of the file signature.
|
||||
$latestPackage = $packages | Sort-Object { (Get-AuthenticodeSignature $_.FullName).SignerCertificate.NotBefore } -Descending | Select-Object -First 1
|
||||
# Removing all packages that are not the latest version
|
||||
WriteLog "Latest version of $StoreAppName has been identified as $latestPackage. Removing old versions of $StoreAppName that may have downloaded."
|
||||
foreach ($package in $packages) {
|
||||
if ($package.FullName -ne $latestPackage) {
|
||||
try {
|
||||
WriteLog "Removing $($package.FullName)"
|
||||
Remove-Item -Path $package.FullName -Force
|
||||
}
|
||||
catch {
|
||||
WriteLog "Failed to delete: $($package.FullName) - $_"
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Apps {
|
||||
param (
|
||||
[string]$AppList
|
||||
)
|
||||
$apps = Get-Content -Path $AppList -Raw | ConvertFrom-Json
|
||||
if (-not $apps) {
|
||||
WriteLog "No apps were specified in AppList.json file."
|
||||
return
|
||||
}
|
||||
$wingetApps = $apps.apps | Where-Object { $_.source -eq "winget" }
|
||||
# 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
|
||||
$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 {
|
||||
Get-WinGetApp -WinGetAppName $wingetApp.Name -WinGetAppId $wingetApp.Id
|
||||
}
|
||||
catch {
|
||||
WriteLog "Error occurred while processing $wingetApp : $_"
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($storeApps) {
|
||||
if (-not (Test-Path -Path $storeAppsFolder -PathType Container)) {
|
||||
New-Item -Path $storeAppsFolder -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
foreach ($storeApp in $storeApps) {
|
||||
try {
|
||||
Get-StoreApp -StoreAppName $storeApp.Name -StoreAppId $storeApp.Id
|
||||
}
|
||||
catch {
|
||||
WriteLog "Error occurred while processing $storeApp : $_"
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-KBLink {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -1747,58 +2039,121 @@ 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) {
|
||||
# #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
|
||||
}
|
||||
}
|
||||
@@ -2224,7 +2579,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 +2607,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,8 +2626,9 @@ 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
|
||||
$WinPEISOName = 'WinPE_FFU_Capture.iso'
|
||||
#Remove-Item -Path "$WinPEFFUPath\media\boot\bootfix.bin" -Force | Out-null
|
||||
# $WinPEISOName = 'WinPE_FFU_Capture.iso'
|
||||
$WinPEISOFile = $CaptureISO
|
||||
$Capture = $false
|
||||
}
|
||||
If ($Deploy) {
|
||||
@@ -2279,18 +2646,32 @@ 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'
|
||||
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"
|
||||
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
|
||||
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`" `"$WinPEISOFile`""
|
||||
|
||||
}
|
||||
elseif($WindowsArch -eq 'arm64'){
|
||||
$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"
|
||||
WriteLog "Cleaning up $WinPEFFUPath"
|
||||
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force
|
||||
@@ -2640,10 +3021,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) {
|
||||
@@ -2787,8 +3178,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"
|
||||
@@ -2800,6 +3199,14 @@ 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 "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"
|
||||
$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"
|
||||
@@ -2877,6 +3284,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
|
||||
@@ -2942,6 +3362,10 @@ if ($InstallApps) {
|
||||
exit
|
||||
}
|
||||
WriteLog "$AppsPath\InstallAppsandSysprep.cmd found"
|
||||
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
|
||||
@@ -2995,9 +3419,10 @@ 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"
|
||||
Start-BitsTransferWithRetry -Source $DefenderDefURL -Destination "$DefenderPath\mpam-fe.exe"
|
||||
WriteLog "Defender Definitions downloaded to $DefenderPath\mpam-fe.exe"
|
||||
}
|
||||
@@ -3023,7 +3448,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"
|
||||
@@ -3063,6 +3495,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"
|
||||
@@ -3205,7 +3642,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
|
||||
}
|
||||
@@ -3344,6 +3786,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 {
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $True, Position = 0)]
|
||||
$DeployISOPath,
|
||||
[Switch]$DisableAutoPlay
|
||||
)
|
||||
$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator'
|
||||
|
||||
if($DeployISOPath){
|
||||
$DevelopmentPath = $DeployISOPath | Split-Path
|
||||
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 Get-USBDrive {
|
||||
$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='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"
|
||||
exit 1
|
||||
}
|
||||
return $USBDrives, $USBDrivesCount
|
||||
}
|
||||
Function Build-DeploymentUSB{
|
||||
param(
|
||||
[Array]$Drives
|
||||
)
|
||||
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
|
||||
Writelog "Creating partitions..."
|
||||
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 + ":\"
|
||||
$jobScriptBlock = {
|
||||
param (
|
||||
[string]$SFolder,
|
||||
[string]$DFolder
|
||||
)
|
||||
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 $FFUPath, $Destination | 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){
|
||||
Writelog "Building $DrivesCount drives concurrently...Please be patient..."
|
||||
}else{
|
||||
Writelog "Building the imaging tool on $model...Please be patient..."
|
||||
}
|
||||
Get-Job | Wait-Job | Out-Null
|
||||
|
||||
Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null
|
||||
Writelog "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
|
||||
if($Last){
|
||||
writelog "All drives selected"
|
||||
}else{
|
||||
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)
|
||||
|
||||
$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
|
||||
}
|
||||
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){
|
||||
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!"
|
||||
}
|
||||
#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-USBDrive
|
||||
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
|
||||
}
|
||||
@@ -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){
|
||||
@@ -117,40 +147,22 @@ $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"
|
||||
|
||||
#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,11 +438,11 @@ 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
|
||||
$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'
|
||||
@@ -444,50 +456,41 @@ 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) {
|
||||
WriteLog 'Setting recovery partition attributes'
|
||||
$diskpartScript = @(
|
||||
"SELECT DISK $($Disk.Number)",
|
||||
"SELECT PARTITION $($recoveryPartition.PartitionNumber)",
|
||||
"GPT ATTRIBUTES=0x8000000000000001",
|
||||
"EXIT"
|
||||
)
|
||||
$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
|
||||
@@ -495,22 +498,14 @@ $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
|
||||
# {
|
||||
# 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"
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
<settings pass="specialize">
|
||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="arm64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ComputerName>MyComputer</ComputerName>
|
||||
</component>
|
||||
</settings>
|
||||
</unattend>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
Reference in New Issue
Block a user