mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b6b5efd8d | |||
| db62e05275 | |||
| 3db66eb55b | |||
| 4709177bc3 | |||
| ce7af09f25 | |||
| e3da438225 | |||
| edbb7ccabe | |||
| f4360b34d9 | |||
| 0ed0cf4aa2 | |||
| 61fc2198c9 | |||
| f45f5a899b | |||
| 37f6dce344 | |||
| 10624787fe | |||
| d7a697d68d | |||
| 97954f59c3 | |||
| 3b23e9f420 | |||
| 94712ecbfc | |||
| a2c2b69026 | |||
| 6abc6f9d1a | |||
| db0fbfaaf4 | |||
| a59210c559 | |||
| add11b0037 | |||
| c26034a89c | |||
| 9287464eb8 | |||
| 1c103b2db7 | |||
| ed6a5fc7f1 | |||
| 768efc8cf7 | |||
| 39a9bc9022 | |||
| f90b7b3c9b | |||
| 802a225c3e | |||
| 74370db5de | |||
| db788c3c30 | |||
| eae619e7e8 | |||
| 0f3380e91e | |||
| 7c80486d88 | |||
| 1f65198803 | |||
| 0f18b7bd80 | |||
| b89f4a3b6b | |||
| 658d2d7af4 | |||
| f09c404f65 | |||
| fbe8eca263 | |||
| 923e8b070d | |||
| e6e53e566f | |||
| f144f1d71c | |||
| 3694e1a6e4 | |||
| d45b6dc8dc | |||
| 5194133a78 | |||
| 6e5d634af6 | |||
| 32d5ff3b47 | |||
| 5545554d7e | |||
| 6a0faa958e | |||
| 15c0478710 | |||
| bab9804022 | |||
| c93c417dba | |||
| 2fe91de000 | |||
| 412e3a078c | |||
| b6dda55a82 | |||
| b8bda93e8d | |||
| ac485f9c87 | |||
| 4b33627d19 | |||
| af28624e2d | |||
| 3457aedf5d | |||
| 5acac4ba5b | |||
| 0b151f9054 | |||
| cafc45dbba | |||
| e3cbcab6b2 | |||
| ec7e9a546c | |||
| 7a2aab3204 | |||
| 5dcdd2c36f | |||
| fb0a630bfd | |||
| 47cd0deb03 | |||
| 378941cd5c | |||
| 1da28024cc | |||
| 480e6a62b6 | |||
| 807456de86 | |||
| c8ef42ab21 | |||
| 60d147c71d | |||
| cd36150ddc | |||
| 198a544dbb | |||
| 40616776eb | |||
| 20c9cf8ab3 | |||
| 17558f86aa | |||
| 7408dbb435 | |||
| 9c1fc59af9 | |||
| 31c785b5da | |||
| 5b93135ebb | |||
| 5f4cf0c66e | |||
| ddbf2b0339 | |||
| e62d481405 | |||
| 6c07ac8595 | |||
| 7d74feec0c | |||
| 6b2a4bcb27 | |||
| 6da9ece0d8 | |||
| dc4438dcf9 | |||
| e250e2a130 | |||
| dad51fdf80 | |||
| d60b0301c5 | |||
| db3e09650a | |||
| bcb9911cd0 | |||
| 67e3035c38 | |||
| 94dd256889 | |||
| cc383c84cb | |||
| b20b614f5e | |||
| 31984e104e | |||
| 94f74a194d | |||
| eaa58e6804 | |||
| 351c87ab96 | |||
| 13e2765c3f | |||
| c049840baa | |||
| e1ab74e5a3 | |||
| 02d858f27f | |||
| 1d8e9f352d | |||
| 7b59e3d0ec | |||
| 213da61389 | |||
| 9de55eb186 | |||
| e3bec5ff45 | |||
| 1bfc4735d3 | |||
| 49b742b47b | |||
| 7f79e50f72 | |||
| 39b9d06d21 | |||
| 047881934a | |||
| 689808eca7 | |||
| 70571a3b49 | |||
| 06138ebaff | |||
| 81a3b10a06 | |||
| 8ba88f4626 | |||
| 9d4b66851a | |||
| 3ba0da19f8 | |||
| 6826f854ae | |||
| afa524091c | |||
| f14c7f2b00 |
+202
-1
@@ -1,5 +1,204 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
# 2412.1
|
||||||
|
|
||||||
|
This is a major release with a number of quality-of-life improvements that will reduce the time it takes to create FFUs. I highly recommend you update to this release.
|
||||||
|
|
||||||
|
## Windows Server Support
|
||||||
|
Thanks to [JonasKloseBW](https://github.com/JonasKloseBW) we have added support for Windows Server! This includes support for Windows Server 2016 through 2025 and supports both core and desktop experience. It will require you to provide your own Server ISO
|
||||||
|
using the `-ISOPath` parameter since we can't automatically download it like we can with client. You also will want to set the `-WindowsSKU` parameter to either `'Standard', 'Datacenter', 'Standard (Desktop Experience)', or 'Datacenter (Desktop Experience)'` depending on your needs.
|
||||||
|
|
||||||
|
Cumulative Updates for Windows and .NET should work as expected. Defender updates should work too. If you notice anything that doesn't work, open an issue.
|
||||||
|
|
||||||
|
## VHDX Caching Support
|
||||||
|
Thanks again to Jonas for adding VHDX caching support #89. For those of you that might be making many FFUs for different configurations, instead of building the VHDX every time, you can cache the VHDX and re-use it for your next build. In testing, this seems to save about 10 minutes, depending on how you're installing Windows (via MCT download, or your own ISO and how old your media is).
|
||||||
|
|
||||||
|
The way this works is a VHDXCache folder is created in the FFUDevelopment folder. If `-AllowVHDXCaching $true`, we store the VHDX file and a config file that keeps track of the following info
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"VhdxFileName": "_FFU-808829869.vhdx",
|
||||||
|
"LogicalSectorSizeBytes": 512,
|
||||||
|
"WindowsSKU": "Pro",
|
||||||
|
"WindowsRelease": "11",
|
||||||
|
"WindowsVersion": "24H2",
|
||||||
|
"OptionalFeatures": "",
|
||||||
|
"IncludedUpdates": [
|
||||||
|
{
|
||||||
|
"Name": "windows11.0-kb5043080-x64_953449672073f8fb99badb4cc6d5d7849b9c83e8.msu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "windows11.0-kb5045934-x64-ndp481_fa9c3adfb0532eb8f4e521f4fb92a179380184c5.msu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "windows11.0-kb5048667-x64_d4ad0ca69de9a02bc356757581e0e0d6960c9f93.msu"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The VHDX files are cached before boot, so they've never been sysprepped. On subsequent runs, if `-AllowVHDXCaching $true` is set, we search the VHDXCache folder, loop through any config files, and look to see if we find one that matches the build information you've passed to the script. If a match is found, robocopy copies in the VHDX and uses the cached VHDX to build the FFU VM.
|
||||||
|
|
||||||
|
## Configuration File Support
|
||||||
|
A configuration file can now be used to configure the parameters in lieu of, or in conjunction with, parameters specified on the command line. Configuration files are especially helpful for those making FFUs for different models, Windows releases, application sets, and more.
|
||||||
|
|
||||||
|
To use, run:
|
||||||
|
`.\BuildFFUVM.ps1 -ConfigFile 'C:\FFUDevelopment\config\Sample_default.json' -verbose`
|
||||||
|
|
||||||
|
### Creating your own Configuration Json file
|
||||||
|
|
||||||
|
If you have a command line that you’ve been using for awhile and would like to convert it to a json file automatically, run your command line like normal, adding
|
||||||
|
`-exportConfigFile 'C:\FFUDevelopment\config\YourConfigFile.json'`
|
||||||
|
to the end of the command. Doing this will generate a well-formatted json file with your configuration settings.
|
||||||
|
|
||||||
|
You can also temporarily overwrite parameters while using a config file. Using the following sample command:
|
||||||
|
|
||||||
|
`.\BuildFFUVM.ps1 -ConfigFile 'C:\FFUDevelopment\config\Sample_default.json' -verbose`
|
||||||
|
|
||||||
|
If you’d like to not include Office (the Sample_default.json file installs Office), you’d add `-InstallOffice $False` to the command line
|
||||||
|
|
||||||
|
`.\BuildFFUVM.ps1 -ConfigFile 'C:\FFUDevelopment\config\Sample_default.json' -verbose -InstallOffice $False`
|
||||||
|
|
||||||
|
Doing this will temporarily overwrite whatever is in the json for the `InstallOffice` parameter. It will not modify the json file. If you would like to change the json file, you can add `-exportConfigFile 'C:\FFUDevelopment\config\Sample_default.json'` and that will overwrite the json file with the new parameter.
|
||||||
|
|
||||||
|
`.\BuildFFUVM.ps1 -ConfigFile 'C:\FFUDevelopment\config\Sample_default.json' -verbose -InstallOffice $False -exportConfigFile 'C:\FFUDevelopment\config\Sample_default.json'`
|
||||||
|
|
||||||
|
## Custom FFU Naming Support
|
||||||
|
Thanks to Jonas, we now have custom FFU naming support. A new parameter -CustomFFUNameTemplate has been added.
|
||||||
|
|
||||||
|
This parameter sets a custom FFU output name with placeholders. Allowed placeholders are:
|
||||||
|
|
||||||
|
`{WindowsRelease}, {WindowsVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt}`
|
||||||
|
|
||||||
|
And below is a description of what to expect when you use each placeholder.
|
||||||
|
|
||||||
|
```
|
||||||
|
{WindowsRelease} = 10, 11, 2016, 2019, 2022, 2025
|
||||||
|
{WindowsVersion} = 1607, 1809, 21h2, 22h2, 23h2, 24h2, etc
|
||||||
|
{SKU} = Home, Home N, Home Single Language, Education, Education N, Pro, Pro N, Pro Education, Pro Education N, Pro for Workstations, Pro N for Workstations, Enterprise, Enterprise N, Standard, Standard (Desktop Experience), Datacenter, Datacenter (Desktop Experience)
|
||||||
|
{BuildDate} = e.g. Dec2024
|
||||||
|
{yyyy} = e.g. 2024
|
||||||
|
{MM} = 2 digit month format (e.g. 12 for December)
|
||||||
|
{dd} = Day of the month in 2 digit format (19)
|
||||||
|
{HH} = Current hour in 24-hour format (e.g., 14 for 2 PM)
|
||||||
|
{hh} = Current hour in 12-hour format (e.g., 02 for 2 PM)
|
||||||
|
{mm} = Current minute in 2-digit format (e.g., 09)
|
||||||
|
{tt} = Current AM/PM designator (e.g., AM or PM)
|
||||||
|
```
|
||||||
|
|
||||||
|
An example for Windows 11 24h2 Pro built today would be:
|
||||||
|
|
||||||
|
`-CustomFFUNameTemplate '{WindowsRelease}_{WindowsVersion}_{SKU}_{yyyy}-{MM}-{dd}_{HH}{mm}'`
|
||||||
|
|
||||||
|
Would result in a FFU file name of:
|
||||||
|
|
||||||
|
`Win11_24h2_Pro_2024-12-20_1225.ffu`
|
||||||
|
|
||||||
|
You can also mix in static text in the name
|
||||||
|
|
||||||
|
`-CustomFFUNameTemplate '{WindowsRelease}_{WindowsVersion}_{SKU}_Office_{yyyy}-{MM}-{dd}_{HH}{mm}'`
|
||||||
|
|
||||||
|
Would result in:
|
||||||
|
|
||||||
|
`Win11_24h2_Pro_Office_2024-12-20_1225.ffu`
|
||||||
|
|
||||||
|
## Additional PRs added
|
||||||
|
#79 Includes the latest Microsoft Software Removal Tool from `@zehadialam` - use `-UpdateLatestMSRT $true`
|
||||||
|
|
||||||
|
#72 Includes some Unattend Sample files from @HedgeComp in the `$FFUDevelopment\Unattend` folder
|
||||||
|
|
||||||
|
#74 Includes some improvements to the USBImagingToolCreator.ps1 file from @w0
|
||||||
|
|
||||||
|
#103 Includes some additional improvements to the USBImagingToolCreator.ps1 file from @MKellyCBSD
|
||||||
|
|
||||||
|
## Misc Fixes
|
||||||
|
- Added server skus to validateset for $WindowsSKU
|
||||||
|
- Added new variable $installationType which uses $WindowsRelease to determine Server or Client. If $installationType is Server, $WindowsRelease version is used to set $WindowsVersion to the appropriate version (1607, 1809, 21H2)
|
||||||
|
- Fixed an issue where the recovery partition wouldn't be created on server OSes due to winre.wim being hidden. Never saw this on client OSes even though it also was hidden IIRC.
|
||||||
|
- Removed verbosity for Optimize-Volume as it was outputting when -verbose was not specified.
|
||||||
|
- Modified some search strings for .NET CUs when installing on Server OS
|
||||||
|
- Included SSU for Windows Server 2016 as it's mandatory
|
||||||
|
- Added some error checking for Server 2019 and 2022 CU installations when CU fails due to lack of SSU. If you run into this error, you're using old media and should use the latest. Always use the latest ISO if you can.
|
||||||
|
- $WindowsVersion set to 24h2, can override by using -WindowsVersion 23H2 if you want the old behavior
|
||||||
|
- Removed the "Downloading information GUID" messages when downloading content from the Microsoft Update Catalog while -verbose was specified in the command line
|
||||||
|
- In Get-KBLink, made a change to just grab the first result returned instead of the entire results page. This removes the need to use a break statement in Save-KB when downloading updates. This fixed an issue with the new 24H2 Checkpoint Cumulative Updates in Win11 and Server 2025.
|
||||||
|
- Changed Windows MSRT search string for x64 and x86. This was mainly to get x64 to the top of the search results. x86 won't actually download since the code isn't in place for content from the MU Catalog to download x86 content (no idea if anyone actually builds x86 FFUs for Win10 - I hope not)
|
||||||
|
- If not passing an ISO, hardcoded WindowsVersion of 22H2 for Windows 10 or 24H2 for Windows 11 since the ESD media only provides those two versions. Not doing this allowed for unnecessary VHDX creation since it checks the WindowsVersion via the json file. This also fixes an issue where CUs could be searched for that didn’t exist, but the media would still download
|
||||||
|
- Added some additional logging entries
|
||||||
|
- Removed verbose output of the Optimize-Volume command
|
||||||
|
- Fixed an issue where not passing an ISO caused the script to fail
|
||||||
|
- Cleaned up the KBPath folder at the end of the run
|
||||||
|
- Changed some minor formatting items
|
||||||
|
- Added Get-Childprocesses function to return child processes of parent process
|
||||||
|
- Added a new -Wait boolean parameter to Invoke-Process function. This is to control whether Invoke-Process should wait in order to track stdout and stderr output. This is needed for processes that may hang, waiting for user input and there isn't a way to bypass (some Intel drivers provided by Dell leave dialog windows even when running silently)
|
||||||
|
-Invoke-Process now returns process information (returns $cmd). This allows for process tracking when calling the function.
|
||||||
|
- Since Invoke-Process now returns the process information, also needed to add Out-Null to the majority of the Invoke-Process references to prevent Invoke-Process from writing to the terminal
|
||||||
|
- Refactored a lot of the Get-DellDrivers function due to inconsistencies with how driver extraction behaves between client and server devices. For client, /s /e seemed to work fine, but for server it would only extract the driver installer content and other dell related files, rather than the driver files themselves. We have since switched to using /s /drivers= which will extract the driver content. Not all drivers support /drivers= and may output some information to the terminal that looks like help documentation. If a driver doesn't support /drivers, the script falls back to using /s /e to do the extraction. If this doesn't work for you, you can always provide your own drivers that you manually download from Dell's website.
|
||||||
|
- Updated Malicious Software Removal Tool (MSRT) code to handle Windows Server
|
||||||
|
- Refactored the Get-ODTURL function to fix recent download issues. Also added some better error handling
|
||||||
|
- Moved the odtsetup.exe download to the FFUDevelopment folder and will clean it up after office has downloaded
|
||||||
|
Updated parameter definition block to be alphabetized (not to be confused by the param block, which is not alphabetized)
|
||||||
|
- Added $PEDriversFolder script variable to the param block (for some reason it was missing)
|
||||||
|
- Added ConfigFile and ExportConfigFile parameters to support json config files
|
||||||
|
- Changed Version to 2412.1
|
||||||
|
- Modified vhdxCacheItem class to include $LogicalSectorSizeBytes
|
||||||
|
- Added new function Get-Parameters to help with new config and export config file functionality
|
||||||
|
- Fixed Get-MicrosoftDrivers function to not require the HTMLFILE COM object, which isn't available in Windows 11. It seems to be installed with Office, which is what was allowing downloads to work and masked the issue.
|
||||||
|
- Added long path support to prevent issues with oscdimg creating the Apps.iso.
|
||||||
|
- Fixed an issue where the $PEDriversFolder variable wasn't being used (instead $FFUDevelopment\PEDrivers was used)
|
||||||
|
- Created a new function New-FFUFileName - this works in conjunction with the new $CustomFFUNameTemplate. The function was needed to support both scenarios where $InstallApps is either $true or $false.
|
||||||
|
- Added new function Export-ConfigFile. When passing -ExportConfigFile 'Path\To\ConfigFile.json' the script will generate a parameter dump of all of the configured parameters
|
||||||
|
- Added driver folder validation to throw an error if spaces are detected in the folder name of the drivers folder (e.g. C:\FFUDevelopment\Drivers\Dell 3190). This is due to an issue with Dell drivers and their inability to handle paths with spaces consistently.
|
||||||
|
- Added back the Windows Security Platform update which grabs it from the web instead of the Microsoft update catalog
|
||||||
|
- Fixed an issue where the Drivers folder was being completely deleted instead of its sub-folders
|
||||||
|
- Removed the Requires -PSEdition Desktop. The script works with both Desktop and Core, so pwsh 7 is fine.
|
||||||
|
- Created a new config folder to hold config files. A new sample_default.json file is provided to show what the format looks like.
|
||||||
|
- You can now set the computername in the unattend.xml file where ever you want. Prior it required that the computername was the first component element.
|
||||||
|
|
||||||
|
## **2409.1**
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fix an issue with removal of Defender/OneDrive/Edge after FFU is complete
|
||||||
|
- Migrate Winget downloads to use [Export-WingetPackage cmdlet](https://github.com/microsoft/winget-cli/blob/master/doc/specs/%23658%20-%20WinGet%20Download.md#winget-powershell-cmdlet) as per issue #50
|
||||||
|
- Add support for preview updates https://github.com/rbalsleyMSFT/FFU/pull/51 - thanks to @HedgeComp
|
||||||
|
- Refactor validation of Unattend/prefixes, PPKG, Autopilot to check for these files early in the process, similar to how we check for drivers
|
||||||
|
- Add better logging when unable to find HDD when applying FFU. Will inform to add WinPE drivers to Deployment Media if HDD not found.
|
||||||
|
- Remove ValidateScript on InstallDrivers and break it out in a validation block so -Make and -Model can be specified anywhere in the command line
|
||||||
|
- Add validation for VMHostIPAddress and VMSwtichName and inform the user if these don't match. Should prevent issues where the FFU isn't getting created.
|
||||||
|
- Removed installation of the Windows Security Platform Update as it has been removed from the MU Catalog. See issue #58
|
||||||
|
- Thanks to w0 for PR #54 to change the validation set for WindowsSKU
|
||||||
|
- Thanks to @zehadialam for PR #60 to fix an issue with Windows boot loader for certain devices where Windows Boot Manager is not the first boot entry after the FFU is applied.
|
||||||
|
- Thanks to @HedgeComp for PR #64 and PR #65
|
||||||
|
|
||||||
|
## **2408.1**
|
||||||
|
|
||||||
|
### External Drive Support
|
||||||
|
|
||||||
|
Up until now, the USB build process has supported using drives identified by Windows as removable drives. Most USB sticks will identify as removable, however faster drives may show up as external hard disk media. You may also have a smaller, portable SSD drive that you'd like to use for imaging since these are typically much faster than regular USB 3.x thumb drives.
|
||||||
|
|
||||||
|
In adding this support, I do realize that there is potential for data loss for those that might have external hard drives attached to their machines.
|
||||||
|
|
||||||
|
To handle this, with help from [HedgeComp](https://github.com/HedgeComp), we've refactored the `Get-USBDrives` function. Two new variables have been created:
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| AllowExternalHardDiskMedia | Bool | If `$true`, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined. |
|
||||||
|
| PromptExternalHardDiskMedia | Bool | If `$true` and AllowExternalHardDiskMedia is `$true`, the script will prompt to select which drive to use. When set to `$true`, only a single drive will be created. If `$false`, the script won't prompt for which external hard disk to use and can use multiple external hard disks, similar to how removable USB drives function. |
|
||||||
|
|
||||||
|
By default, this functionality won't effect previous USB drive creation behavior. However if you want to take advantage of the new functionality, set `-AllowExternalHardDiskMedia $true`
|
||||||
|
|
||||||
|
Fixes/misc
|
||||||
|
|
||||||
|
- Fixed a display issue where if multiple FFU files were in the FFU folder, the script wouldn't display which FFUs to choose from when running the script without -verbose. This will now display a table with the last modified date whether you run with the -verbose switch or not.
|
||||||
|
- Added start/end/duration time (thanks [HedgeComp](https://github.com/HedgeComp))
|
||||||
|
- Fixed an issue where deployment media wasn't prompting for a key to be pressed as expected
|
||||||
|
- Fixed an issue when creating the USB drive and the drive had a RAW partition style that clear-disk would generate an error
|
||||||
|
- Cleaned up some commented code
|
||||||
|
- Added Create-PEMedia.ps1 as a helper script to quickly generate Deploy or Capture media
|
||||||
|
- Fixed an issue with clean up of Defender/OneDrive/Edge
|
||||||
|
- Fixed an issue with the formatting of InstallAppsandSysprep.cmd file
|
||||||
|
- Updated parameter documentation in the script to include newly added parameters
|
||||||
|
|
||||||
## **2407.1**
|
## **2407.1**
|
||||||
|
|
||||||
This is another major release that includes:
|
This is another major release that includes:
|
||||||
@@ -16,7 +215,7 @@ To support the newly released Copilot+ PCs, we now support the creation and depl
|
|||||||
* The host machine you're building the FFU from must be ARM64
|
* 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.
|
* 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.
|
* 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
|
* Drivers - Surface Laptop 7 and Pro 11 don't have ARM64 drivers available yet (there are entries, but they just point to a .txt file). Other OEMs may have drivers available.
|
||||||
|
|
||||||
In all, testing has gone very well.
|
In all, testing has gone very well.
|
||||||
|
|
||||||
@@ -43,6 +242,8 @@ In order to get apps to help build your AppList.json file, just run `winget sear
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
When downloading msstore apps, it does require an Entra ID. If you're building your FFUs from a machine that is not signed in with an Entra ID, you will be prompted for credentials for each app you download AND for the license file for each app (2 prompts per app). If downloading many store apps is something you plan on doing, I highly recommend signing in with an Entra ID to prevent the authentication prompts.
|
||||||
|
|
||||||
Other improvements
|
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)
|
* [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)
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ REM d:\Office\setup.exe /configure d:\office\DeployFFU.xml
|
|||||||
REM Install Defender Platform Update
|
REM Install Defender Platform Update
|
||||||
REM Install Defender Definitions
|
REM Install Defender Definitions
|
||||||
REM Install Windows Security Platform Update
|
REM Install Windows Security Platform Update
|
||||||
|
REM Install Windows Malicious Software Removal Tool
|
||||||
REM Install OneDrive Per Machine
|
REM Install OneDrive Per Machine
|
||||||
REM Install Edge Stable
|
REM Install Edge Stable
|
||||||
REM Winget Win32 Apps
|
REM Winget Win32 Apps
|
||||||
|
REM START Batch variables placeholder
|
||||||
|
REM END Batch variables placeholder
|
||||||
REM Add additional apps below here
|
REM Add additional apps below here
|
||||||
REM Contoso App (Example)
|
REM Contoso App (Example)
|
||||||
REM msiexec /i d:\Contoso\setup.msi /qn /norestart
|
REM msiexec /i d:\Contoso\setup.msi /qn /norestart
|
||||||
@@ -30,11 +33,8 @@ for /d %%D in ("%basepath%\*") do (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@REM for %%F in ("!appfolder!\*.xml") do (
|
|
||||||
@REM set "licensefile=%%F"
|
|
||||||
@REM )
|
|
||||||
if defined mainpackage (
|
if defined mainpackage (
|
||||||
set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!""
|
set "dism_command=DISM /Online /Add-ProvisionedAppxPackage /PackagePath:"!mainpackage!" /Region:all /StubPackageOption:installfull"
|
||||||
if exist "!dependenciesfolder!" (
|
if exist "!dependenciesfolder!" (
|
||||||
for %%G in ("!dependenciesfolder!\*") do (
|
for %%G in ("!dependenciesfolder!\*") do (
|
||||||
set "dism_command=!dism_command! /DependencyPackagePath:"%%G""
|
set "dism_command=!dism_command! /DependencyPackagePath:"%%G""
|
||||||
@@ -48,7 +48,6 @@ for /d %%D in ("%basepath%\*") do (
|
|||||||
) else (
|
) else (
|
||||||
set "dism_command=!dism_command! /SkipLicense"
|
set "dism_command=!dism_command! /SkipLicense"
|
||||||
)
|
)
|
||||||
set "dism_command=!dism_command! /Region:All"
|
|
||||||
echo !dism_command!
|
echo !dism_command!
|
||||||
!dism_command!
|
!dism_command!
|
||||||
)
|
)
|
||||||
|
|||||||
+1843
-572
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
|||||||
|
param (
|
||||||
|
[string]$FFUDevelopmentPath = $PSScriptRoot,
|
||||||
|
[string]$adkPath = 'C:\Program Files (x86)\Windows Kits\10\',
|
||||||
|
[string]$WindowsArch = 'x64',
|
||||||
|
[bool]$CopyPEDrivers = $false,
|
||||||
|
[string]$CaptureISO = "$PSScriptRoot\WinPE_FFU_Capture_x64.iso",
|
||||||
|
[string]$DeployISO = "$PSScriptRoot\WinPE_FFU_Deploy_x64.iso",
|
||||||
|
[string]$LogFile = "$PSScriptRoot\Create-PEMedia.log",
|
||||||
|
[bool]$Capture,
|
||||||
|
[bool]$Deploy = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
function WriteLog($LogText) {
|
||||||
|
Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Verbose $LogText
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = $true;
|
||||||
|
}
|
||||||
|
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 - $Logfile for more info"
|
||||||
|
throw $_
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-PEMedia {
|
||||||
|
param (
|
||||||
|
[Parameter()]
|
||||||
|
[bool]$Capture,
|
||||||
|
[Parameter()]
|
||||||
|
[bool]$Deploy
|
||||||
|
)
|
||||||
|
#Need to use the Demployment and Imaging tools environment to create winPE media
|
||||||
|
$DandIEnv = "$adkPath`Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat"
|
||||||
|
$WinPEFFUPath = "$FFUDevelopmentPath\WinPE"
|
||||||
|
|
||||||
|
If (Test-path -Path "$WinPEFFUPath") {
|
||||||
|
WriteLog "Removing old WinPE path at $WinPEFFUPath"
|
||||||
|
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force | out-null
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLog "Copying WinPE files to $WinPEFFUPath"
|
||||||
|
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'
|
||||||
|
|
||||||
|
WriteLog 'Mounting WinPE media to add WinPE optional components'
|
||||||
|
Mount-WindowsImage -ImagePath "$WinPEFFUPath\media\sources\boot.wim" -Index 1 -Path "$WinPEFFUPath\mount" | Out-Null
|
||||||
|
WriteLog 'Mounting complete'
|
||||||
|
|
||||||
|
$Packages = @(
|
||||||
|
"WinPE-WMI.cab",
|
||||||
|
"en-us\WinPE-WMI_en-us.cab",
|
||||||
|
"WinPE-NetFX.cab",
|
||||||
|
"en-us\WinPE-NetFX_en-us.cab",
|
||||||
|
"WinPE-Scripting.cab",
|
||||||
|
"en-us\WinPE-Scripting_en-us.cab",
|
||||||
|
"WinPE-PowerShell.cab",
|
||||||
|
"en-us\WinPE-PowerShell_en-us.cab",
|
||||||
|
"WinPE-StorageWMI.cab",
|
||||||
|
"en-us\WinPE-StorageWMI_en-us.cab",
|
||||||
|
"WinPE-DismCmdlets.cab",
|
||||||
|
"en-us\WinPE-DismCmdlets_en-us.cab"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
WriteLog "Adding Package $Package"
|
||||||
|
Add-WindowsPackage -Path "$WinPEFFUPath\mount" -PackagePath $PackagePath | Out-Null
|
||||||
|
WriteLog "Adding package complete"
|
||||||
|
}
|
||||||
|
If ($Capture) {
|
||||||
|
WriteLog "Copying $FFUDevelopmentPath\WinPECaptureFFUFiles\* to WinPE capture media"
|
||||||
|
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'
|
||||||
|
$WinPEISOFile = $CaptureISO
|
||||||
|
# $Capture = $false
|
||||||
|
}
|
||||||
|
If ($Deploy) {
|
||||||
|
WriteLog "Copying $FFUDevelopmentPath\WinPEDeployFFUFiles\* to WinPE deploy media"
|
||||||
|
Copy-Item -Path "$FFUDevelopmentPath\WinPEDeployFFUFiles\*" -Destination "$WinPEFFUPath\mount" -Recurse -Force | Out-Null
|
||||||
|
WriteLog 'Copy complete'
|
||||||
|
#If $CopyPEDrivers = $true, add drivers to WinPE media using dism
|
||||||
|
if ($CopyPEDrivers) {
|
||||||
|
WriteLog "Adding drivers to WinPE media"
|
||||||
|
try {
|
||||||
|
Add-WindowsDriver -Path "$WinPEFFUPath\Mount" -Driver "$FFUDevelopmentPath\PEDrivers" -Recurse -ErrorAction SilentlyContinue | Out-null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog 'Some drivers failed to be added to the FFU. This can be expected. Continuing.'
|
||||||
|
}
|
||||||
|
WriteLog "Adding drivers complete"
|
||||||
|
}
|
||||||
|
# $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
|
||||||
|
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 $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'){
|
||||||
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:2`#p0,e,b`"$OSCDIMGPath\etfsboot.com`"`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif($WindowsArch -eq 'arm64'){
|
||||||
|
if($Capture){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys_noprompt.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
$OSCDIMGArgs = "-m -o -u2 -udfver102 -bootdata:1`#pEF,e,b`"$OSCDIMGPath\Efisys.bin`" `"$WinPEFFUPath\media`" `"$WinPEISOFile`""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Invoke-Process $OSCDIMG $OSCDIMGArgs
|
||||||
|
WriteLog "ISO created successfully"
|
||||||
|
WriteLog "Cleaning up $WinPEFFUPath"
|
||||||
|
Remove-Item -Path "$WinPEFFUPath" -Recurse -Force
|
||||||
|
WriteLog 'Cleanup complete'
|
||||||
|
}
|
||||||
|
if($Capture){
|
||||||
|
New-PEMedia -Capture $Capture
|
||||||
|
}
|
||||||
|
if($Deploy){
|
||||||
|
New-PEMedia -Deploy $Deploy
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -4,22 +4,34 @@ param(
|
|||||||
$DeployISOPath,
|
$DeployISOPath,
|
||||||
[Switch]$DisableAutoPlay
|
[Switch]$DisableAutoPlay
|
||||||
)
|
)
|
||||||
$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator'
|
$Host.UI.RawUI.WindowTitle = 'Imaging Tool USB Creator'
|
||||||
|
|
||||||
if($DeployISOPath){
|
if($DeployISOPath){
|
||||||
$DevelopmentPath = $DeployISOPath | Split-Path
|
$DevelopmentPath = $DeployISOPath | Split-Path
|
||||||
|
$ImagesPath = "$DevelopmentPath\FFU"
|
||||||
function WriteLog($LogText) {
|
function WriteLog($LogText) {
|
||||||
$LogFileName = '\Script.log'
|
$LogFileName = '\Script.log'
|
||||||
$LogFile = $DevelopmentPath + $LogFilename
|
$LogFile = $DevelopmentPath + $LogFilename
|
||||||
Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue
|
Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue
|
||||||
Write-Verbose $LogText
|
Write-Verbose $LogText
|
||||||
}
|
}
|
||||||
Function Get-USBDrive {
|
|
||||||
$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'")
|
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)) {
|
If($USBDrives -and ($null -eq $USBDrives.count)) {
|
||||||
$USBDrivesCount = 1
|
$USBDrivesCount = 1
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
$USBDrivesCount = $USBDrives.Count
|
$USBDrivesCount = $USBDrives.Count
|
||||||
}
|
}
|
||||||
WriteLog "Found $USBDrivesCount USB drives"
|
WriteLog "Found $USBDrivesCount USB drives"
|
||||||
@@ -27,20 +39,23 @@ Function Get-USBDrive {
|
|||||||
if ($null -eq $USBDrives) {
|
if ($null -eq $USBDrives) {
|
||||||
WriteLog "No removable USB drive found. Exiting"
|
WriteLog "No removable USB drive found. Exiting"
|
||||||
Write-Error "No removable USB drive found. Exiting"
|
Write-Error "No removable USB drive found. Exiting"
|
||||||
|
Pause
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
return $USBDrives, $USBDrivesCount
|
return $USBDrives, $USBDrivesCount
|
||||||
}
|
}
|
||||||
|
|
||||||
Function Build-DeploymentUSB{
|
Function Build-DeploymentUSB{
|
||||||
param(
|
param(
|
||||||
[Array]$Drives
|
[Array]$Drives
|
||||||
)
|
)
|
||||||
writelog "Checking if ffu files are present in the ffu folder"
|
writelog "Creating list of FFU image files"
|
||||||
$Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse
|
$Images = Get-ChildItem -Path $ImagesPath -Filter "*.ffu" -File -Recurse
|
||||||
writelog "Checking if drivers are present in the drivers folder"
|
writelog "Checking if drivers are present in the drivers folder"
|
||||||
$Drivers = Get-ChildItem -Path $DriversPath -Recurse
|
$Drivers = Get-ChildItem -Path $DriversPath -Recurse
|
||||||
$DrivesCount = $Drives.Count
|
$DrivesCount = $Drives.Count
|
||||||
Writelog "Creating partitions..."
|
Write-ProgressLog "Create Imaging Tool" "Creating partitions..."
|
||||||
|
writelog "Create job to partition each usb drive"
|
||||||
foreach ($USBDrive in $Drives) {
|
foreach ($USBDrive in $Drives) {
|
||||||
$DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "")
|
$DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "")
|
||||||
$Model = $USBDrive.model
|
$Model = $USBDrive.model
|
||||||
@@ -97,11 +112,19 @@ $Destination = $Drive + ":\"
|
|||||||
[string]$SFolder,
|
[string]$SFolder,
|
||||||
[string]$DFolder
|
[string]$DFolder
|
||||||
)
|
)
|
||||||
|
New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null
|
||||||
Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J
|
Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLog "Start job to copy all FFU files to $Destination"
|
WriteLog "Start job to copy all FFU files to $Destination"
|
||||||
Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $FFUPath, $Destination | Out-Null
|
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){
|
if($Drivers){
|
||||||
@@ -128,14 +151,14 @@ if(!($Drivers)){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if($DrivesCount -gt 1){
|
if($DrivesCount -gt 1){
|
||||||
Writelog "Building $DrivesCount drives concurrently...Please be patient..."
|
Write-ProgressLog "Create Imaging Tool" "Building $DrivesCount drives concurrently...Please be patient..."
|
||||||
} else {
|
} else {
|
||||||
Writelog "Building the imaging tool on $model...Please be patient..."
|
Write-ProgressLog "Create Imaging Tool" "Building the imaging tool on $model...Please be patient..."
|
||||||
}
|
}
|
||||||
Get-Job | Wait-Job | Out-Null
|
Get-Job | Wait-Job | Out-Null
|
||||||
|
|
||||||
Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null
|
Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null
|
||||||
Writelog "Drive creation jobs completed..."
|
Write-ProgressLog "Create Imaging Tool" "Drive creation jobs completed..."
|
||||||
}
|
}
|
||||||
|
|
||||||
Function New-DeploymentUSB {
|
Function New-DeploymentUSB {
|
||||||
@@ -167,20 +190,15 @@ Function New-DeploymentUSB {
|
|||||||
$var = $true
|
$var = $true
|
||||||
$DriveSelected = Read-Host 'Enter the drive number to apply the .iso to'
|
$DriveSelected = Read-Host 'Enter the drive number to apply the .iso to'
|
||||||
$DriveSelected = ($DriveSelected -as [int]) -1
|
$DriveSelected = ($DriveSelected -as [int]) -1
|
||||||
if($Last){
|
writelog "Drive $DriveSelected selected"
|
||||||
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'
|
Write-Host 'Input was not in correct format. Please enter a valid FFU number'
|
||||||
$var = $false
|
$var = $false
|
||||||
}
|
}
|
||||||
} until (($DriveSelected -le $Count -1 -or $last) -and $var)
|
} 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"
|
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
|
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD
|
||||||
}
|
}
|
||||||
@@ -199,10 +217,10 @@ Function New-DeploymentUSB {
|
|||||||
}
|
}
|
||||||
WriteLog "Setting the registry key to re-enable autoplay for all drives"
|
WriteLog "Setting the registry key to re-enable autoplay for all drives"
|
||||||
if($DisableAutoPlay){
|
if($DisableAutoPlay){
|
||||||
Writelog "Setting disable autoplay setting back to $DisableAutoPlayCurrentSetting"
|
Write-ProgressLog "Create Imaging Tool" "Enabling Autoplay"
|
||||||
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value $DisableAutoPlayCurrentSetting -Type DWORD
|
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 0 -Type DWORD
|
||||||
}
|
}
|
||||||
Writelog "Completed!"
|
Write-ProgressLog "Create Imaging Tool" "Completed!"
|
||||||
}
|
}
|
||||||
#Get USB Drive and create log file
|
#Get USB Drive and create log file
|
||||||
if(Test-Path "$DevelopmentPath\Script.log"){
|
if(Test-Path "$DevelopmentPath\Script.log"){
|
||||||
@@ -211,8 +229,11 @@ New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out
|
|||||||
}
|
}
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
WriteLog 'Getting USB drive information and usb drive count'
|
WriteLog 'Getting USB drive information and usb drive count'
|
||||||
$USBDrives,$USBDrivesCount = Get-USBDrive
|
$USBDrives,$USBDrivesCount = Get-RemovableDrive
|
||||||
|
WriteLog 'Setting first step for percentage progress bar'
|
||||||
|
$currentStep = 1
|
||||||
New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount
|
New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount
|
||||||
|
|
||||||
read-host -Prompt "USB drive creation complete. Press ENTER to exit"
|
read-host -Prompt "USB drive creation complete. Press ENTER to exit"
|
||||||
|
|
||||||
Exit
|
Exit
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#Modify the net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
#Modify the net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
||||||
net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
||||||
|
#Custom naming placeholder
|
||||||
|
|
||||||
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
|
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
|
||||||
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
|
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
|
||||||
@@ -11,7 +12,10 @@ reg load "HKLM\FFU" $Software
|
|||||||
|
|
||||||
$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID'
|
$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID'
|
||||||
[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild'
|
[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild'
|
||||||
$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
|
if ($CurrentBuild -notin 14393, 17763) {
|
||||||
|
$WindowsVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
|
||||||
|
}
|
||||||
|
$InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType'
|
||||||
$BuildDate = Get-Date -uformat %b%Y
|
$BuildDate = Get-Date -uformat %b%Y
|
||||||
|
|
||||||
$SKU = switch ($SKU) {
|
$SKU = switch ($SKU) {
|
||||||
@@ -28,36 +32,69 @@ $SKU = switch ($SKU) {
|
|||||||
EducationN { 'EduN' }
|
EducationN { 'EduN' }
|
||||||
ProfessionalWorkstation { 'Pro_Wks' }
|
ProfessionalWorkstation { 'Pro_Wks' }
|
||||||
ProfessionalWorkstationN { 'Pro_WksN' }
|
ProfessionalWorkstationN { 'Pro_WksN' }
|
||||||
|
ServerStandard { 'Srv_Std' }
|
||||||
|
ServerDatacenter { 'Srv_Dtc' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($InstallationType -eq "Client") {
|
||||||
if ($CurrentBuild -ge 22000) {
|
if ($CurrentBuild -ge 22000) {
|
||||||
$Name = 'Win11'
|
$WindowsRelease = 'Win11'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$Name = 'Win10'
|
$WindowsRelease = 'Win10'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$WindowsRelease = switch ($CurrentBuild) {
|
||||||
|
26100 { '2025' }
|
||||||
|
20348 { '2022' }
|
||||||
|
17763 { '2019' }
|
||||||
|
14393 { '2016' }
|
||||||
|
Default { $WindowsVersion }
|
||||||
|
}
|
||||||
|
if ($InstallationType -eq "Server Core") {
|
||||||
|
$SKU += "_Core"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($CustomFFUNameTemplate) {
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsRelease}', $WindowsRelease
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsVersion}', $WindowsVersion
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{SKU}', $SKU
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{BuildDate}', $BuildDate
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{yyyy}', (Get-Date -UFormat '%Y')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{MM}', (Get-Date -UFormat '%m')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{dd}', (Get-Date -UFormat '%d')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{HH}', (Get-Date -UFormat '%H')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{hh}', (Get-Date -UFormat '%I')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{mm}', (Get-Date -UFormat '%M')
|
||||||
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{tt}', (Get-Date -UFormat '%p')
|
||||||
|
if($CustomFFUNameTemplate -notlike '*.ffu') {
|
||||||
|
$CustomFFUNameTemplate += '.ffu'
|
||||||
|
}
|
||||||
|
$dismArgs = "/capture-ffu /imagefile=W:\$CustomFFUNameTemplate /capturedrive=\\.\PhysicalDrive0 /name:$WindowsRelease$WindowsVersion$SKU /Compress:Default"
|
||||||
|
} else {
|
||||||
#If Office is installed, modify the file name of the FFU
|
#If Office is installed, modify the file name of the FFU
|
||||||
#$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null
|
#$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null
|
||||||
$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue
|
$Office = Get-ChildItem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue
|
||||||
if ($Office) {
|
if ($Office) {
|
||||||
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu"
|
$ffuFilePath = "W:\$WindowsRelease`_$WindowsVersion`_$SKU`_Office`_$BuildDate.ffu"
|
||||||
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
|
} else {
|
||||||
|
$ffuFilePath = "W:\$WindowsRelease`_$WindowsVersion`_$SKU`_Apps`_$BuildDate.ffu"
|
||||||
|
|
||||||
}
|
}
|
||||||
else{
|
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$WindowsRelease$WindowsVersion$SKU /Compress:Default"
|
||||||
$ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Apps`_$BuildDate.ffu"
|
|
||||||
$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Unload Registry
|
#Unload Registry
|
||||||
Set-Location X:\
|
Set-Location X:\
|
||||||
Remove-Variable SKU
|
Remove-Variable SKU
|
||||||
Remove-Variable CurrentBuild
|
Remove-Variable CurrentBuild
|
||||||
Remove-Variable DisplayVersion
|
if ($CurrentBuild -notin 14393, 17763) {
|
||||||
|
Remove-Variable WindowsVersion
|
||||||
|
}
|
||||||
|
if($Office) {
|
||||||
Remove-Variable Office
|
Remove-Variable Office
|
||||||
|
}
|
||||||
reg unload "HKLM\FFU"
|
reg unload "HKLM\FFU"
|
||||||
#This prevents Critical Process Died errors you can have during deployment of the FFU - may not happen during capture from WinPE, but adding here to be consistent with VHDX capture
|
#This prevents Critical Process Died errors you can have during deployment of the FFU - may not happen during capture from WinPE, but adding here to be consistent with VHDX capture
|
||||||
Write-Host "Sleeping for 60 seconds to allow registry to unload prior to capture"
|
Write-Host "Sleeping for 60 seconds to allow registry to unload prior to capture"
|
||||||
@@ -65,5 +102,4 @@ Start-sleep 60
|
|||||||
Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null
|
Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null
|
||||||
#Copy DISM log to Host
|
#Copy DISM log to Host
|
||||||
xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null
|
xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null
|
||||||
|
|
||||||
wpeutil Shutdown
|
wpeutil Shutdown
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ function Get-USBDrive(){
|
|||||||
return $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(){
|
function Get-HardDrive(){
|
||||||
$SystemInfo = Get-WmiObject -Class 'Win32_ComputerSystem'
|
$SystemInfo = Get-WmiObject -Class 'Win32_ComputerSystem'
|
||||||
$Manufacturer = $SystemInfo.Manufacturer
|
$Manufacturer = $SystemInfo.Manufacturer
|
||||||
@@ -59,11 +55,18 @@ function Set-DiskpartAnswerFiles($DiskpartFile,$DiskID){
|
|||||||
|
|
||||||
function Set-Computername($computername){
|
function Set-Computername($computername){
|
||||||
[xml]$xml = Get-Content $UnattendFile
|
[xml]$xml = Get-Content $UnattendFile
|
||||||
if($xml.unattend.settings.component.Count -ge 2){
|
$components = $xml.unattend.settings.component
|
||||||
#Assumes that Computername is the first component element
|
$found = $false
|
||||||
$xml.unattend.settings.component[0].ComputerName = $computername
|
foreach ($component in $components) {
|
||||||
}else{
|
if ($component.ComputerName) {
|
||||||
$xml.unattend.settings.component.ComputerName = $computername
|
$component.ComputerName = $computername
|
||||||
|
$found = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $found) {
|
||||||
|
WriteLog 'ComputerName element not found in unattend.xml.'
|
||||||
|
throw 'ComputerName element not found in unattend.xml.'
|
||||||
}
|
}
|
||||||
$xml.Save($UnattendFile)
|
$xml.Save($UnattendFile)
|
||||||
return $computername
|
return $computername
|
||||||
@@ -127,33 +130,23 @@ function Invoke-Process {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 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
|
#Get USB Drive and create log file
|
||||||
$LogFileName = 'ScriptLog.txt'
|
$LogFileName = 'ScriptLog.txt'
|
||||||
$USBDrive = Get-USBDrive
|
$USBDrive = Get-USBDrive
|
||||||
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
||||||
$LogFile = $USBDrive + $LogFilename
|
$LogFile = $USBDrive + $LogFilename
|
||||||
$version = '2407.1'
|
$version = '2412.3'
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
WriteLog "Script version: $version"
|
WriteLog "Script version: $version"
|
||||||
|
|
||||||
#Find PhysicalDrive
|
#Find PhysicalDrive
|
||||||
# $PhysicalDeviceID = Get-HardDrive
|
# $PhysicalDeviceID = Get-HardDrive
|
||||||
$hardDrive = Get-HardDrive
|
$hardDrive = Get-HardDrive
|
||||||
|
if($null -eq $hardDrive){
|
||||||
|
WriteLog 'No hard drive found. Exiting'
|
||||||
|
WriteLog 'Try adding storage drivers to the PE boot image (you can re-create your FFU and USB drive and add the PE drivers to the PEDrivers folder and add -CopyPEDrivers $true to the command line, or manually add them via DISM)'
|
||||||
|
Exit
|
||||||
|
}
|
||||||
$PhysicalDeviceID = $hardDrive.DeviceID
|
$PhysicalDeviceID = $hardDrive.DeviceID
|
||||||
$BytesPerSector = $hardDrive.BytesPerSector
|
$BytesPerSector = $hardDrive.BytesPerSector
|
||||||
WriteLog "Physical BytesPerSector is $BytesPerSector"
|
WriteLog "Physical BytesPerSector is $BytesPerSector"
|
||||||
@@ -432,10 +425,6 @@ If (Test-Path -Path $Drivers)
|
|||||||
Writelog 'No driver folders found'
|
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
|
#Partition drive
|
||||||
Writelog 'Clean Disk'
|
Writelog 'Clean Disk'
|
||||||
try {
|
try {
|
||||||
@@ -572,6 +561,12 @@ If (Test-Path -Path $Drivers)
|
|||||||
WriteLog 'Copying drivers succeeded'
|
WriteLog 'Copying drivers succeeded'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WriteLog "Setting Windows Boot Manager to be first in the display order."
|
||||||
|
Invoke-Process bcdedit.exe "/set {fwbootmgr} displayorder {bootmgr} /addfirst"
|
||||||
|
WriteLog "Windows Boot Manager has been set to be first in the display order."
|
||||||
|
WriteLog "Setting default Windows boot loader to be first in the display order."
|
||||||
|
Invoke-Process bcdedit.exe "/set {bootmgr} displayorder {default} /addfirst"
|
||||||
|
WriteLog "The default Windows boot loader has been set to be first in the display order."
|
||||||
#Copy DISM log to USBDrive
|
#Copy DISM log to USBDrive
|
||||||
WriteLog "Copying dism log to $USBDrive"
|
WriteLog "Copying dism log to $USBDrive"
|
||||||
invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y"
|
invoke-process xcopy "X:\Windows\logs\dism\dism.log $USBDrive /Y"
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
|
<settings pass="specialize">
|
||||||
|
<!--<ComputerName> must be in the first Component Element "Microsoft-Windows-Shell-Setup" . Do not change the order or remove it -->
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<ComputerName>MYCOMPUTER</ComputerName><!--Leave Default will be renamed-->
|
||||||
|
<TimeZone>Eastern Standard Time</TimeZone><!--Add Your Local TimeZone-->
|
||||||
|
</component>
|
||||||
|
<!-- Place additional Components Elements and Settings below here: -->
|
||||||
|
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<RunASynchronous>
|
||||||
|
<RunASynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Path>cmd.exe /c date 09-07-2024</Path> <!--Set the device clock to the current date. Helpful when BIOS clocks out of sync. -->
|
||||||
|
<Description>Set system date to a specific date</Description>
|
||||||
|
</RunASynchronousCommand>
|
||||||
|
</RunASynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<InputLocale>0409:00000409</InputLocale><!--Set your Keybaord and System Local https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-8.1-and-8/hh825682(v=win.10) -->
|
||||||
|
<SystemLocale>en-US</SystemLocale>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
<UserLocale>en-US</UserLocale>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<OOBE>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC> <!--Disable Diagnostic Data sent to Microsoft-->
|
||||||
|
<HideEULAPage>true</HideEULAPage><!--Hide the End User License agreement -->
|
||||||
|
<HideWirelessSetupInOOBE>false</HideWirelessSetupInOOBE> <!--Show Wifi Setup -->
|
||||||
|
</OOBE>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
<settings pass="specialize">
|
<settings pass="specialize">
|
||||||
|
<!--<ComputerName> must be in the first Component Element "Microsoft-Windows-Shell-Setup" . Do not change the order or remove it -->
|
||||||
<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">
|
<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>
|
<ComputerName>MyComputer</ComputerName>
|
||||||
</component>
|
</component>
|
||||||
|
<!--Place addtional Components Elements and settings below here. -->
|
||||||
</settings>
|
</settings>
|
||||||
</unattend>
|
</unattend>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
<settings pass="specialize">
|
<settings pass="specialize">
|
||||||
|
<!--<ComputerName> must be in the first Component Element "Microsoft-Windows-Shell-Setup" . Do not change the order or remove it -->
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<ComputerName>MyComputer</ComputerName>
|
<ComputerName>MyComputer</ComputerName>
|
||||||
</component>
|
</component>
|
||||||
|
<!--Place addtional Components Elements and settings below here. -->
|
||||||
</settings>
|
</settings>
|
||||||
</unattend>
|
</unattend>
|
||||||
@@ -1,19 +1,113 @@
|
|||||||
# Using Full Flash Update (FFU) files to speed up Windows deployment
|
# Using Full Flash Update (FFU) files to speed up Windows deployment
|
||||||
|
|
||||||
This repo contains the full FFU process that we use in US Education at Microsoft to help customers with large deployments of Windows as they prepare for the new school year. This process isn't limited to only large deployments at the start of the year, but is the most common.
|
What if you could have a Windows image (Windows 10/11 or Server) that has:
|
||||||
|
|
||||||
This process will copy Windows in about 2-3 minutes to the target device, optionally copy drivers, provisioning packages, Autopilot, etc. School technicians have even given the USB sticks to teachers and teachers calling them their "Magic USB sticks" to quickly get student devices reimaged in the event of an issue with their Windows PC.
|
- The latest Windows cumulative update
|
||||||
|
- The latest .NET cumulative update
|
||||||
|
- The latest Windows Defender Platform and Definition Updates
|
||||||
|
- The latest version of Microsoft Edge
|
||||||
|
- The latest version of OneDrive (Per-Machine)
|
||||||
|
- The latest version of Microsoft 365 Apps/Office
|
||||||
|
- The latest drivers from any of the major OEMs (Dell, HP, Lenovo, Microsoft) (yes, the latest, not some out of date enterprise CAB file from years ago)
|
||||||
|
- Winget support so you can integrate any app available from Winget directly in your image
|
||||||
|
- ARM64 support for the latest Copilot+ PCs
|
||||||
|
- The ability to bring your own drivers and apps if necessary
|
||||||
|
- Custom WinRE support
|
||||||
|
|
||||||
While we use this in Education at Microsoft, other industries can use it as well. We esepcially see a need for something like this with partners who do re-imaging on behalf of customers. The difference in Education is that they typically have large deployments that tend to happen at the beginning of the school year and any amount of time saved is helpful. Microsoft Deployment Toolkit, Configuration Manager, and other community solutions are all great solutions, but are typically slower due to WIM deployments being file-based while FFU files are sector-based.
|
And the best part: it takes less than two minutes to apply the image, even with all of these updates added to the media. After setting Windows up and going through Autopilot or a provisioning package, total elapsed time ~10 minutes (depending on what Intune or your device management tool is deploying).
|
||||||
|
|
||||||
|
The Full-Flash update (FFU) process can automatically download the latest release of Windows 11, the updates mentioned above, and creates a USB drive that can be used to quickly reimage a machine.
|
||||||
|
|
||||||
# Updates
|
# Updates
|
||||||
|
|
||||||
2406.1 has been released! Check out the changes in the new [Change Log](ChangeLog.md)
|
2412.1 has been released! Check out the changes in the [Change Log](ChangeLog.md)
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
If you're not familiar with Github, you can click the Green code button above and select download zip. Extract the zip file and make sure to copy the FFUDevelopment folder to the root of your C: drive. That will make it easy to follow the guide and allow the scripts to work properly.
|
- Download the latest [release](https://github.com/rbalsleyMSFT/FFU/releases)
|
||||||
|
- Extract the FFUDevelopment folder from the ZIP file (recommend to C:\FFUDevelopment)
|
||||||
|
- Follow the doc: C:\FFUDevelopment\Docs\BuildDeployFFU.docx
|
||||||
|
|
||||||
If extracted correctly, your c:\FFUDevelopment folder should look like the following. If it does, go to c:\FFUDevelopment\Docs\BuildDeployFFU.docx to get started.
|
## YouTube Detailed Walkthrough
|
||||||
|
|
||||||

|
The first 15 minutes of the following video includes a quick start demo to get started. Below the video are a list of chapters. This video was taken with the 2407.2 build. Features released after that will not be demonstrated in the video.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=rqXRbgeeKSQ "Reimage Windows Fast with Full-Flash Update (FFU))")
|
||||||
|
|
||||||
|
Chapters:
|
||||||
|
|
||||||
|
[00:00](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=0s) Begin
|
||||||
|
|
||||||
|
[03:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=201s) Quick Start Prereqs
|
||||||
|
|
||||||
|
[07:19](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=439s) Quick Start Demo
|
||||||
|
|
||||||
|
[14:12](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=852s) Script Parameters
|
||||||
|
|
||||||
|
[17:22](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1042s) Obtaining Windows Media
|
||||||
|
|
||||||
|
[25:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1555s) Adding Applications
|
||||||
|
|
||||||
|
[26:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1619s) Adding M365 Apps/Office
|
||||||
|
|
||||||
|
[29:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=1761s) Adding Applications via Winget
|
||||||
|
|
||||||
|
[34:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2099s) Bring your own Applications
|
||||||
|
|
||||||
|
[36:01](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2161s) Customizing InstallAppsAndSysprep.cmd
|
||||||
|
|
||||||
|
[38:34](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2314s) Demo - Application Configuration
|
||||||
|
|
||||||
|
[49:43](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=2983s) Drivers
|
||||||
|
|
||||||
|
[55:39](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3339s) Automatically downloading drivers
|
||||||
|
|
||||||
|
[57:28](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3448s) Microsoft Surface drivers
|
||||||
|
|
||||||
|
[58:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3535s) Dell drivers
|
||||||
|
|
||||||
|
[01:01:45](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3705s) Lenovo drivers
|
||||||
|
|
||||||
|
[01:03:16](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3796s) HP drivers
|
||||||
|
|
||||||
|
[01:05:25](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3925s) Bring your own drivers
|
||||||
|
|
||||||
|
[01:06:24](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=3984s) Demo - Drivers
|
||||||
|
|
||||||
|
[01:11:55](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4315s) Multi-model driver support
|
||||||
|
|
||||||
|
[01:13:21](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4401s) Device naming
|
||||||
|
|
||||||
|
[01:18:30](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4710s) Device enrollment
|
||||||
|
|
||||||
|
[01:21:43](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=4903s) Autopilot
|
||||||
|
|
||||||
|
[01:24:57](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5097s) Provisioning packages
|
||||||
|
|
||||||
|
[01:26:54](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5214s) Custom WinRE
|
||||||
|
|
||||||
|
[01:29:59](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5399s) Demo - Putting it all together (Deep dive)
|
||||||
|
|
||||||
|
[01:32:06](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5526s) Downloading Lenovo 500w drivers
|
||||||
|
|
||||||
|
[01:33:28](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5608s) Downloading apps via Winget
|
||||||
|
|
||||||
|
[01:36:54](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5814s) Downloading Office, Defender, Edge, OneDrive
|
||||||
|
|
||||||
|
[01:38:15](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5895s) Building the Apps.iso
|
||||||
|
|
||||||
|
[01:39:08](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=5948s) Applying Windows to the VHDX
|
||||||
|
|
||||||
|
[01:40:16](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6016s) Downloading and applying cumulative updates
|
||||||
|
|
||||||
|
[01:41:44](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6104s) Building the VM
|
||||||
|
|
||||||
|
[01:48:13](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6493s) Capturing the FFU
|
||||||
|
|
||||||
|
[01:53:38](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=6818s) Creating USB drive
|
||||||
|
|
||||||
|
[01:58:41](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=7121s) Deploying FFU
|
||||||
|
|
||||||
|
[02:11:48](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=7908s) Troubleshooting
|
||||||
|
|
||||||
|
[02:14:30](https://www.youtube.com/watch?v=rqXRbgeeKSQ&t=8070s) EDU Endpoint Office Hours
|
||||||
|
|||||||
Reference in New Issue
Block a user