Skip to content
Merged
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
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ Repository moved from `Stensel8/WinDeploy` to `THectic-NL/WinDeploy`.
- All `Stensel8/WinDeploy` references (README, SECURITY.md, `Docs/autounattend.xml`, `Scripts/Start.ps1`, `Scripts/Deploy.ps1`, old changelog release links) now point at `THectic-NL/WinDeploy`.
- `.github/workflows/validate.yml`: the syntax-check and helper-function-test jobs merged into one job (same runner, one less billed minute), path-scoped to `Scripts/**`, otherwise unchanged.
- `CONTRIBUTING.md`, `.github/pull_request_template.md`: extended with the site's bilingual-content and Hugo-build checks, alongside the existing Windows-testing requirement, which stays required for anything under `Scripts/` or `Docs/`.
- `Apply-Tweaks.ps1` / `Deploy.ps1`: the WinUtil `Standard` preset now runs by default (`-Tweaks` defaults to `Yes`, including under `-NonInteractive`) instead of asking Y/N. Pass `-Tweaks No` to skip it, or `-Tweaks Ask` to get the old prompt back.
- README's "Option 3" `windeploy.stensel.nl` one-liner removed. It pointed at the old repository and isn't part of this one; Option 2 (direct execution against the latest `THectic-NL/WinDeploy` release) is the only one-liner now.

### Fixed
- `Start.ps1`: installing PowerShell 7 via WinGet was checked only once, 5 seconds after the install command returned. On a slower box that check could run before the install had actually finished writing `pwsh.exe`, so the script treated a still-succeeding WinGet install as failed and ran the MSI installer as well - installing PowerShell 7 twice (two separate Start Menu entries). Now polls for up to 20 seconds and logs WinGet's own exit code before falling back.
- `Set-HostName.ps1`: exited with code 1 when no valid BIOS serial number was found (e.g. on VMs, or hardware without a real factory serial) even though this is an expected, already-handled condition with the same "set it manually" guidance the other non-fatal branches in this script give. `Deploy.ps1` counted that exit code as a failed step, so a run with nothing else wrong still ended with "Some deployment steps failed." Now exits 0, matching every other branch in the script.

### Removed
- `.github/workflows/codeql.yml`, `dependency-review.yml`, `security.yml`, `stale.yml`, in favour of the leaner CI set above (PSScriptAnalyzer + Trivy + actionlint, no DevSkim/Semgrep/CodeQL-for-Actions/dependency-review/stale-bot). This mirrors what already happened to BypassNRO; flagging it here since it is a real reduction in automated security-scanning coverage, not just a rename.

### Notes
- `windeploy.stensel.nl` (the "Option 3" one-liner in the README) is an external redirect that pointed at the old repository. It is not part of this repository and needs to be repointed or retired separately.

---

## [0.8.0] - 2026-08-29
Expand Down
37 changes: 13 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,6 @@ Zero-touch Windows deployment with automatic driver updates, application install
iex (irm "https://raw.githubusercontent.com/THectic-NL/WinDeploy/$((irm https://api.github.com/repos/THectic-NL/WinDeploy/releases/latest).tag_name)/Scripts/Start.ps1")
```

### Option 3: One-liner

> [!NOTE]
> `windeploy.stensel.nl` pointed at Option 2 before this project moved to THectic-NL. That redirect needs to be repointed (or retired) separately — it isn't part of this repository.

```powershell
# Run as Administrator in PowerShell 7
iex (irm windeploy.stensel.nl)
```

---

## How It Works
Expand All @@ -84,11 +74,8 @@ graph TD
K3 --> L
L[Install Applications]
L --> M[Remove Bloatware]
M --> M2{Run WinUtil tweaks?}
M2 -->|Y| M3[Apply WinUtil preset]
M2 -->|N / timeout| N
M3 --> N
N[Apply Theme]
M --> M3[Apply WinUtil Standard preset]
M3 --> N[Apply Theme]
N --> O[Set Hostname]
O --> P[Install Windows Updates]
P --> Q[Complete]
Expand All @@ -98,14 +85,14 @@ graph TD

### 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.
BitLocker (in `Harden-Windows.ps1`) asks Y/N before running; it times out after 90 seconds and defaults to **No**, so an unattended run never stalls. The WinUtil tweaks (`Apply-Tweaks.ps1`) run automatically with the `Standard` preset - pass `-Tweaks No` to skip them, or `-Tweaks Ask` to be prompted instead. Everything else is applied automatically.

```powershell
.\Deploy.ps1 -NonInteractive # no prompts, both skipped
.\Deploy.ps1 -BitLocker Yes -Tweaks Yes # no prompts, both applied
.\Deploy.ps1 -NonInteractive # no prompts; BitLocker skipped, WinUtil Standard preset still runs
.\Deploy.ps1 -BitLocker Yes -Tweaks No # no prompts; BitLocker applied, WinUtil skipped
```

The `autounattend.xml` USB deployment passes `-NonInteractive` automatically.
The `autounattend.xml` USB deployment passes `-NonInteractive` automatically, so it also gets the WinUtil `Standard` preset unattended.

---

Expand Down Expand Up @@ -171,14 +158,16 @@ Opt-in, asks Y/N. On yes: `C:` is encrypted with XTS-AES-256 (used space only, T

---

## Optional tweaks (WinUtil)
## WinUtil tweaks

`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.
`Apply-Tweaks.ps1` runs a [WinUtil](https://github.com/ChrisTitusTech/winutil) preset in its own process, by default with no prompt. 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
.\Apply-Tweaks.ps1 # Standard preset, no prompt (default)
.\Apply-Tweaks.ps1 -Tweaks No # skip it
.\Apply-Tweaks.ps1 -Tweaks Ask # ask Y/N instead
.\Apply-Tweaks.ps1 -Preset Minimal
.\Apply-Tweaks.ps1 -Preset Advanced # also removes OneDrive, widgets, Windows AI
```

---
Expand Down
8 changes: 6 additions & 2 deletions Scripts/Deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ param(
[ValidateSet('Ask', 'Yes', 'No')]
[string]$BitLocker = 'Ask',

# Defaults to Yes: the WinUtil Standard preset's QoL tweaks (disables
# telemetry, consumer features, Delivery Optimization, etc.) are wanted on
# every deployment, not opt-in. Pass -Tweaks No to skip it, or -Tweaks Ask
# to be prompted instead.
[ValidateSet('Ask', 'Yes', 'No')]
[string]$Tweaks = 'Ask'
[string]$Tweaks = 'Yes'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'

if ($NonInteractive) {
if ($BitLocker -eq 'Ask') { $BitLocker = 'No' }
if ($Tweaks -eq 'Ask') { $Tweaks = 'No' }
if ($Tweaks -eq 'Ask') { $Tweaks = 'Yes' }
}

# Waits for Enter, but never longer than $TimeoutSeconds, so an unattended
Expand Down
11 changes: 6 additions & 5 deletions Scripts/Deployment/Apply-Tweaks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
# Applies a WinUtil (ChrisTitusTech) tweak preset.
# Standalone script - can be deployed via any management tool.
#
# This step downloads and runs a THIRD-PARTY script from christitus.com, so it
# is opt-in: the operator has to confirm with Y before anything runs.
# This step downloads and runs a THIRD-PARTY script from christitus.com. It
# runs by default (-Tweaks Yes) since stock Windows' defaults are exactly what
# it's meant to fix; pass -Tweaks No to skip it or -Tweaks Ask to be prompted.
# ============================================================================

#requires -Version 5.1
#requires -RunAsAdministrator

[CmdletBinding()]
param(
# Ask = prompt the operator (default)
# Yes = run without prompting
# Ask = prompt the operator
# Yes = run without prompting (default)
# No = skip this step
[ValidateSet('Ask', 'Yes', 'No')]
[string]$Tweaks = 'Ask',
[string]$Tweaks = 'Yes',

# WinUtil preset to apply. See Get-PresetSummary below for what each does.
[ValidateSet('Standard', 'Minimal', 'Advanced')]
Expand Down
2 changes: 1 addition & 1 deletion Scripts/Deployment/Set-HostName.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ try {
if ([string]::IsNullOrWhiteSpace($Serial) -or $Serial.Length -lt 5) {
Write-DeployLog "No valid serial number found. Unable to set hostname automatically. Please set the hostname manually." -IsError:$true
Write-Output "No serial number found. Please set the computer name manually."
exit 1
exit 0
}
$LastFive = $Serial.Substring($Serial.Length - 5).ToUpper()
$Hostname = "PC-$LastFive"
Expand Down
25 changes: 19 additions & 6 deletions Scripts/Start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,26 @@ function Install-Pwsh7 {
Write-Host "Attempting installation via WinGet..." -ForegroundColor Cyan
try {
& winget install --id Microsoft.PowerShell --silent --accept-package-agreements --accept-source-agreements 2>&1
Start-Sleep -Seconds 5
Update-EnvironmentPath
$path = Get-Pwsh7Path
if ($path) {
Write-Host "PowerShell 7 installed successfully via WinGet" -ForegroundColor Green
return $true
$wingetExitCode = $LASTEXITCODE
if ($wingetExitCode -and $wingetExitCode -ne 0) {
Write-Warning "WinGet install returned exit code $wingetExitCode"
}

# winget itself already waited for the installer to finish, but the
# file/registry write can lag a moment behind that. Poll instead of a
# single fixed sleep so a slow-to-appear pwsh.exe isn't mistaken for a
# failed install - that false negative used to trigger the MSI method
# too, installing PowerShell 7 twice.
for ($i = 0; $i -lt 10; $i++) {
Start-Sleep -Seconds 2
Update-EnvironmentPath
$path = Get-Pwsh7Path
if ($path) {
Write-Host "PowerShell 7 installed successfully via WinGet" -ForegroundColor Green
return $true
}
}
Write-Warning "WinGet did not report an error, but pwsh.exe was not found after installing"
} catch {
Write-Warning "WinGet installation failed: $_"
}
Expand Down
4 changes: 2 additions & 2 deletions src/content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ For USB / offline installs via `autounattend.xml`, and the full configuration re
| Hardening | SMB signing, LSA protection, HVCI, Defender ASR rules, screen lock — see below |
| Applications | WinGet + Microsoft 365 via CDN/ODT |
| Bloatware removal | Removes consumer apps, blocks their reinstall via policy |
| Tweaks *(opt-in)* | A [WinUtil](https://github.com/ChrisTitusTech/winutil) preset, behind a Y/N prompt |
| Tweaks | A [WinUtil](https://github.com/ChrisTitusTech/winutil) `Standard` preset, applied automatically |
| Theme, hostname | Dark mode, `PC-<serial>` naming |
| Windows Update | Installs everything available |

Two steps ask before they run: **BitLocker** and the **WinUtil tweaks**. Both time out after 90 seconds and default to no, so an unattended run never stalls. Pass `-NonInteractive` to skip both outright.
**BitLocker** asks before it runs, times out after 90 seconds and defaults to no, so an unattended run never stalls. Pass `-NonInteractive` to skip it outright. The **WinUtil tweaks** apply automatically with no prompt; pass `-Tweaks No` to skip them.

## Security hardening

Expand Down
4 changes: 2 additions & 2 deletions src/content/_index.nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ Voor USB/offline installaties via `autounattend.xml`, en de volledige configurat
| Hardening | SMB signing, LSA-protection, HVCI, Defender ASR-regels, schermvergrendeling — zie hieronder |
| Applicaties | WinGet + Microsoft 365 via CDN/ODT |
| Bloatware verwijderen | Verwijdert consumer-apps, blokkeert herinstallatie via policy |
| Tweaks *(optioneel)* | Een [WinUtil](https://github.com/ChrisTitusTech/winutil)-preset, achter een Y/N-prompt |
| Tweaks | Een [WinUtil](https://github.com/ChrisTitusTech/winutil) `Standard`-preset, automatisch toegepast |
| Thema, hostnaam | Dark mode, `PC-<serienummer>`-naamgeving |
| Windows Update | Installeert alles wat beschikbaar is |

Twee stappen vragen eerst om bevestiging: **BitLocker** en de **WinUtil-tweaks**. Beide lopen na 90 seconden af en kiezen dan standaard nee, zodat een onbeheerde run nooit vastloopt. Geef `-NonInteractive` mee om beide direct over te slaan.
**BitLocker** vraagt eerst om bevestiging, loopt na 90 seconden af en kiest dan standaard nee, zodat een onbeheerde run nooit vastloopt. Geef `-NonInteractive` mee om dit over te slaan. De **WinUtil-tweaks** worden automatisch toegepast, zonder prompt; geef `-Tweaks No` mee om ze over te slaan.

## Security hardening

Expand Down
Loading