Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [0.8.0] - 2026-08-29

### Added
- `Scripts/Deployment/Apply-Tweaks.ps1`. Optional step that applies a [WinUtil](https://github.com/ChrisTitusTech/winutil) preset (`Standard` by default) after a Y/N prompt, listing what the preset changes before you answer. Runs in its own process so a failure there cannot take down the deployment.
- BitLocker now creates a recovery password protector, saves it to the operator's Documents folder and prints it on screen. Previously only a TPM protector was created, leaving the drive unrecoverable after a TPM clear, mainboard swap or firmware change — while the script told the operator to export a recovery key that never existed.
- BitLocker is opt-in via a Y/N prompt (`-BitLocker Ask|Yes|No`). All other hardening still applies unconditionally.
- `-NonInteractive` switch on `Deploy.ps1` and `Start.ps1`, forwarded from `autounattend.xml`, so the USB path stays zero-touch.
- Hardening extended with LSA protection (RunAsPPL), WDigest plaintext caching disabled, anonymous SAM/share enumeration restricted, SMB client and server signing required, insecure SMB guest logons blocked, LLMNR disabled, memory integrity (HVCI) enabled, SMBv1 feature removed, and 9 Defender Attack Surface Reduction rules.
- `Remove-Bloat.ps1` now implements the "prevents reinstall" its header promised, via `DisableWindowsConsumerFeatures` and related CloudContent/Store policies.

### Fixed
- `Docs/autounattend.xml` never launched WinDeploy. The first-logon script was generated as `unattend-02.cmd` but contained PowerShell, which `cmd.exe` cannot run. It is now a `.ps1`, and the generator URL in the header comment was corrected to `FirstLogonScriptType1=Ps1` so regenerating reproduces the fix.
- `Harden-Windows.ps1` set `SMB2 = 0` under `LanmanServer\Parameters`, which disables SMB2 and SMB3 and breaks file and printer sharing. Microsoft advises against it. Removed and replaced with SMB signing and guest-logon hardening.
- `Test-IntuneEnrollment` crashed under `Set-StrictMode` when the `Enrollments` key was absent: `Get-ChildItem -ErrorAction SilentlyContinue` returns `$null`, and `$null.Count` throws.
- `Deploy.ps1` crashed under `Set-StrictMode` on the first step, because `$LASTEXITCODE` is undefined until something sets it. It also never reset between steps, so one failing step marked every later step as failed. Now reset to `0` before each step.
- Screen lock settings were written to `HKCU`, which during deployment belongs to the deployment account rather than the end user. Now written to the machine-wide policy hive. `SCRNSAVE.EXE` was also empty, so Windows never started a screen saver and the secure lock never triggered; it now points at `scrnsave.scr`.
- `winget install` was missing `--silent`, so applications could show installer UI mid-deployment. It now also passes `--exact` and `--disable-interactivity`.
- The Office ODT configuration used `<Display Level="Full" />`, which installs interactively. Now `None`.
- Windows Updates without a KB number (drivers, definitions) were skipped, because `Install-WindowsUpdate -KB $update.KB` cannot install them. Replaced with a single `Get-WindowsUpdate -Install` pass, which is also considerably faster.
- Seven WinGet font error codes were typed as `-1979335xxx` instead of `-1978335xxx`, so they could never match a real exit code.
- `Install-Drivers.ps1` matched HP with `-like "*hp*"`, which also matches manufacturers such as "Sharp". Now matched as a whole token.
- `Install-Drivers.ps1` installed `HPCMSL` without bootstrapping the NuGet provider or trusting PSGallery, so it prompted and stalled, or failed outright. It now does the same bootstrap `Install-WindowsUpdates.ps1` already did.
- `Install-WindowsUpdates.ps1` threw under `Set-StrictMode` if `wuauserv` could not be found, instead of reporting it.
- `Remove-Bloat.ps1` logged to `%TEMP%\WinDeploy\Logs` while every other script and the README use `C:\WinDeploy\Logs`.
- `Remove-Bloat.ps1` used the `` `e `` escape (PowerShell 6+) in a script that declares `#requires -Version 5.1`, where it prints as literal text.
- The RMM step no longer wraps the installer in a background job that `Remove-Job -Force` could kill. `Install-RMMAgent.ps1` already launches the agent detached, so it runs inline like every other step.
- "Press Enter to exit" prompts now time out after 120 seconds instead of blocking an unattended deployment.

### Changed
- Deployment scripts log failures with `Write-Warning` instead of `Write-Error`, which printed a full error record with category and stack trace for every non-fatal skip. `Deploy.ps1` already did this.
- `Remove-Bloat.ps1` bloatware list extended with Windows 11 24H2/25H2 in-box apps: Dev Home, the new Outlook, Edge Game Assist, Cross Device (Phone Link), Start Experiences, Meet Now and the Copilot AI provider.

---

## [0.7.3] - 2026-05-01

### Fixed
Expand Down
8 changes: 4 additions & 4 deletions Docs/autounattend.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<!--https://schneegans.de/windows/unattend-generator/?LanguageMode=Unattended&UILanguage=nl-NL&Locale=nl-NL&Keyboard=00020409&GeoLocation=176&PEMode=Default&WindowsEditionMode=Generic&WindowsEdition=pro&ProcessorArchitecture=amd64&ProcessorArchitecture=arm64&HidePowerShellWindows=true&ComputerNameMode=Script&ComputerNameScript=%23+Get+the+serialnumber+of+the+machine+via+the+CIMinstance+cmdlet%0D%0A%24serial+%3D+%28Get-CimInstance+Win32_BIOS%29.SerialNumber%0D%0A%0D%0A%23+Format+the+serialnumber+to+give+back+the+last+5+characters+prefixed+with+%27PC-%27%0D%0Areturn+%27PC-%7B0%7D%27+-f+%24serial.Substring%28%24serial.Length+-+5%29%0D%0A&TimeZoneMode=Explicit&TimeZone=W.+Europe+Standard+Time&UserAccountMode=Unattended&AccountName0=LocalAdmin&AccountDisplayName0=&AccountPassword0=&AccountGroup0=Administrators&AutoLogonMode=Own&PasswordExpirationMode=Unlimited&LockoutMode=Default&HideFiles=Hidden&ShowFileExtensions=true&LaunchToThisPC=true&ShowEndTask=true&TaskbarSearch=Label&TaskbarIconsMode=Default&DisableWidgets=true&LeftTaskbar=true&DisableBingResults=true&StartTilesMode=Default&StartPinsMode=Default&EnableLongPaths=true&DeleteJunctions=true&DisableLastAccess=true&DisableAppSuggestions=true&HideEdgeFre=true&DisableEdgeStartupBoost=true&MakeEdgeUninstallable=true&DeleteWindowsOld=true&EffectsMode=Default&DesktopIconsMode=Default&StartFoldersMode=Custom&StartFolderSettings=true&WifiMode=Skip&ExpressSettings=DisableAll&LockKeysMode=Configure&CapsLockInitial=Off&CapsLockBehavior=Toggle&NumLockInitial=On&NumLockBehavior=Toggle&ScrollLockInitial=Off&ScrollLockBehavior=Toggle&StickyKeysMode=Disabled&ColorMode=Custom&SystemColorTheme=Dark&AppsColorTheme=Dark&AccentColor=%230078d4&WallpaperMode=Script&WallpaperScript=%5BSystem.IO.File%5D%3A%3AReadAllBytes%28%27C%3A%5CWindows%5CWeb%5CWallpaper%5CWindows%5Cimg19.jpg%27%29%0D%0A&LockScreenMode=Script&LockScreenScript=%5BSystem.IO.File%5D%3A%3AReadAllBytes%28%27C%3A%5CWindows%5CWeb%5CWallpaper%5CWindows%5Cimg19.jpg%27%29%0D%0A&Remove3DViewer=true&RemoveBingSearch=true&RemoveClipchamp=true&RemoveCopilot=true&RemoveCortana=true&RemoveDevHome=true&RemoveFamily=true&RemoveFeedbackHub=true&RemoveGameAssist=true&RemoveInternetExplorer=true&RemoveMailCalendar=true&RemoveMaps=true&RemoveMediaFeatures=true&RemoveMixedReality=true&RemoveZuneVideo=true&RemoveNews=true&RemoveOffice365=true&RemoveOneDrive=true&RemoveOneNote=true&RemoveOneSync=true&RemoveOutlook=true&RemovePaint3D=true&RemovePeople=true&RemovePowerAutomate=true&RemovePowerShell2=true&RemovePowerShellISE=true&RemoveRecall=true&RemoveSkype=true&RemoveSolitaire=true&RemoveSpeech=true&RemoveStepsRecorder=true&RemoveStickyNotes=true&RemoveGetStarted=true&RemoveToDo=true&RemoveVoiceRecorder=true&RemoveWallet=true&RemoveWeather=true&RemoveFaxAndScan=true&RemoveWindowsMediaPlayer=true&RemoveWordPad=true&RemoveXboxApps=true&RemoveYourPhone=true&FirstLogonScript0=Install-Script+winget-install+-Force%0D%0Awinget-install+-Force&FirstLogonScriptType0=Ps1&FirstLogonScript1=iex+%28irm+%22https%3A%2F%2Fraw.githubusercontent.com%2FStensel8%2FWinDeploy%2F%24%28%28irm+https%3A%2F%2Fapi.github.com%2Frepos%2FStensel8%2FWinDeploy%2Freleases%2Flatest%29.tag_name%29%2FScripts%2FStart.ps1%22%29&FirstLogonScriptType1=Cmd&WdacMode=Skip&AppLockerMode=Skip-->
<!--https://schneegans.de/windows/unattend-generator/?LanguageMode=Unattended&UILanguage=nl-NL&Locale=nl-NL&Keyboard=00020409&GeoLocation=176&PEMode=Default&WindowsEditionMode=Generic&WindowsEdition=pro&ProcessorArchitecture=amd64&ProcessorArchitecture=arm64&HidePowerShellWindows=true&ComputerNameMode=Script&ComputerNameScript=%23+Get+the+serialnumber+of+the+machine+via+the+CIMinstance+cmdlet%0D%0A%24serial+%3D+%28Get-CimInstance+Win32_BIOS%29.SerialNumber%0D%0A%0D%0A%23+Format+the+serialnumber+to+give+back+the+last+5+characters+prefixed+with+%27PC-%27%0D%0Areturn+%27PC-%7B0%7D%27+-f+%24serial.Substring%28%24serial.Length+-+5%29%0D%0A&TimeZoneMode=Explicit&TimeZone=W.+Europe+Standard+Time&UserAccountMode=Unattended&AccountName0=LocalAdmin&AccountDisplayName0=&AccountPassword0=&AccountGroup0=Administrators&AutoLogonMode=Own&PasswordExpirationMode=Unlimited&LockoutMode=Default&HideFiles=Hidden&ShowFileExtensions=true&LaunchToThisPC=true&ShowEndTask=true&TaskbarSearch=Label&TaskbarIconsMode=Default&DisableWidgets=true&LeftTaskbar=true&DisableBingResults=true&StartTilesMode=Default&StartPinsMode=Default&EnableLongPaths=true&DeleteJunctions=true&DisableLastAccess=true&DisableAppSuggestions=true&HideEdgeFre=true&DisableEdgeStartupBoost=true&MakeEdgeUninstallable=true&DeleteWindowsOld=true&EffectsMode=Default&DesktopIconsMode=Default&StartFoldersMode=Custom&StartFolderSettings=true&WifiMode=Skip&ExpressSettings=DisableAll&LockKeysMode=Configure&CapsLockInitial=Off&CapsLockBehavior=Toggle&NumLockInitial=On&NumLockBehavior=Toggle&ScrollLockInitial=Off&ScrollLockBehavior=Toggle&StickyKeysMode=Disabled&ColorMode=Custom&SystemColorTheme=Dark&AppsColorTheme=Dark&AccentColor=%230078d4&WallpaperMode=Script&WallpaperScript=%5BSystem.IO.File%5D%3A%3AReadAllBytes%28%27C%3A%5CWindows%5CWeb%5CWallpaper%5CWindows%5Cimg19.jpg%27%29%0D%0A&LockScreenMode=Script&LockScreenScript=%5BSystem.IO.File%5D%3A%3AReadAllBytes%28%27C%3A%5CWindows%5CWeb%5CWallpaper%5CWindows%5Cimg19.jpg%27%29%0D%0A&Remove3DViewer=true&RemoveBingSearch=true&RemoveClipchamp=true&RemoveCopilot=true&RemoveCortana=true&RemoveDevHome=true&RemoveFamily=true&RemoveFeedbackHub=true&RemoveGameAssist=true&RemoveInternetExplorer=true&RemoveMailCalendar=true&RemoveMaps=true&RemoveMediaFeatures=true&RemoveMixedReality=true&RemoveZuneVideo=true&RemoveNews=true&RemoveOffice365=true&RemoveOneDrive=true&RemoveOneNote=true&RemoveOneSync=true&RemoveOutlook=true&RemovePaint3D=true&RemovePeople=true&RemovePowerAutomate=true&RemovePowerShell2=true&RemovePowerShellISE=true&RemoveRecall=true&RemoveSkype=true&RemoveSolitaire=true&RemoveSpeech=true&RemoveStepsRecorder=true&RemoveStickyNotes=true&RemoveGetStarted=true&RemoveToDo=true&RemoveVoiceRecorder=true&RemoveWallet=true&RemoveWeather=true&RemoveFaxAndScan=true&RemoveWindowsMediaPlayer=true&RemoveWordPad=true&RemoveXboxApps=true&RemoveYourPhone=true&FirstLogonScript0=Install-Script+winget-install+-Force%0D%0Awinget-install+-Force&FirstLogonScriptType0=Ps1&FirstLogonScript1=iex+%28irm+%22https%3A%2F%2Fraw.githubusercontent.com%2FStensel8%2FWinDeploy%2F%24%28%28irm+https%3A%2F%2Fapi.github.com%2Frepos%2FStensel8%2FWinDeploy%2Freleases%2Flatest%29.tag_name%29%2FScripts%2FStart.ps1%22%29&FirstLogonScriptType1=Ps1&WdacMode=Skip&AppLockerMode=Skip-->
<settings pass="offlineServicing"></settings>
<settings pass="windowsPE">
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
Expand Down Expand Up @@ -567,8 +567,8 @@ Set-WallpaperImage -LiteralPath 'C:\Windows\Setup\Scripts\Wallpaper';
Install-Script winget-install -Force
winget-install -Force
</File>
<File path="C:\Windows\Setup\Scripts\unattend-02.cmd">
iex (irm "https://raw.githubusercontent.com/Stensel8/WinDeploy/$((irm https://api.github.com/repos/Stensel8/WinDeploy/releases/latest).tag_name)/Scripts/Start.ps1")
<File path="C:\Windows\Setup\Scripts\unattend-02.ps1">
&amp; ([scriptblock]::Create((irm "https://raw.githubusercontent.com/Stensel8/WinDeploy/$((irm https://api.github.com/repos/Stensel8/WinDeploy/releases/latest).tag_name)/Scripts/Start.ps1"))) -NonInteractive
</File>
<File path="C:\Windows\Setup\Scripts\Specialize.ps1">
$scripts = @(
Expand Down Expand Up @@ -814,7 +814,7 @@ $scripts = @(
&amp; 'C:\Windows\Setup\Scripts\unattend-01.ps1';
};
{
C:\Windows\Setup\Scripts\unattend-02.cmd;
&amp; 'C:\Windows\Setup\Scripts\unattend-02.ps1';
};
{
Remove-Item -LiteralPath @(
Expand Down
67 changes: 64 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,35 @@ graph TD
H --> I[Install RMM Agent]
I --> J[Update Drivers]
J --> K[Windows Hardening]
K --> L[Install Applications]
K --> K2{Enable BitLocker?}
K2 -->|Y| K3[Encrypt C: + save recovery key]
K2 -->|N / timeout| L
K3 --> L
L[Install Applications]
L --> M[Remove Bloatware]
M --> N[Apply Theme]
M --> M2{Run WinUtil tweaks?}
M2 -->|Y| M3[Apply WinUtil preset]
M2 -->|N / timeout| N
M3 --> N
N[Apply Theme]
N --> O[Set Hostname]
O --> P[Install Windows Updates]
P --> Q[Complete]
```

`Start.ps1` ensures PowerShell 7 and WinGet are available, handles elevation, and downloads `Deploy.ps1`. `Deploy.ps1` orchestrates the deployment by downloading and executing each script in sequence.

### Interactive steps

BitLocker (in `Harden-Windows.ps1`) and the WinUtil tweaks (`Apply-Tweaks.ps1`) each ask Y/N before running. Both time out after 90 seconds and default to **No**, so an unattended run never stalls. Everything else is applied automatically.

```powershell
.\Deploy.ps1 -NonInteractive # no prompts, both skipped
.\Deploy.ps1 -BitLocker Yes -Tweaks Yes # no prompts, both applied
```

The `autounattend.xml` USB deployment passes `-NonInteractive` automatically.

---

## Configuration
Expand Down Expand Up @@ -118,11 +137,53 @@ Place your agent installer as `Agent.exe` (or any `*agent*.exe`) on the USB driv

---

## Security hardening

`Harden-Windows.ps1` applies these automatically:

| Area | Setting |
|---|---|
| Removable media | AutoRun disabled, `autorun.inf` blocked |
| SMB | SMBv1 feature removed, client + server signing required, insecure guest logons blocked |
| Credentials | LSA protection (RunAsPPL), WDigest plaintext caching off, anonymous SAM/share enumeration restricted |
| Network | LLMNR disabled |
| Code integrity | Memory integrity (HVCI) enabled |
| Defender | 9 Attack Surface Reduction rules enabled |
| Other | Device co-installers disabled, Windows Script Host disabled |
| Screen lock | Secure screen saver after 15 minutes, console lock on resume |

Memory integrity, LSA protection and SMB signing require a restart. Windows Script Host is disabled; a few legacy MSI installers use VBScript custom actions and can fail because of it.

### BitLocker

Opt-in, asks Y/N. On yes: `C:` is encrypted with XTS-AES-256 (used space only, TPM-bound), a recovery password is created, written to your Documents folder and printed on screen.

**Store that key elsewhere and delete the file.** Without it the drive cannot be recovered after a TPM clear, mainboard swap or firmware change.

```powershell
.\Harden-Windows.ps1 -BitLocker Yes # encrypt without prompting
.\Harden-Windows.ps1 -BitLocker No # skip BitLocker, apply the rest
```

---

## Optional tweaks (WinUtil)

`Apply-Tweaks.ps1` runs a [WinUtil](https://github.com/ChrisTitusTech/winutil) preset after a Y/N prompt, in its own process. Standard creates a restore point, then disables activity history, location, telemetry, consumer features, Delivery Optimization and Explorer folder-type auto-discovery, sets non-essential services to manual, and cleans temp files.

```powershell
.\Apply-Tweaks.ps1 -Tweaks Yes # Standard preset
.\Apply-Tweaks.ps1 -Tweaks Yes -Preset Minimal
.\Apply-Tweaks.ps1 -Tweaks Yes -Preset Advanced # also removes OneDrive, widgets, Windows AI
```

---

## Logging

All operations are logged to `C:\WinDeploy\Logs\`:
- `Start.log`. Main entry point log.
- `Install-Drivers.log`, `Install-Applications.log`, etc. Per-script logs.
- `Install-Drivers.log`, `Install-Applications.log`, `Harden-Windows.log`, `Apply-Tweaks.log`, etc. Per-script logs.

View logs in real-time:
```powershell
Expand Down
Loading