Skip to content

Fix deployment bugs, rework hardening, add opt-in BitLocker and WinUtil steps - #48

Open
Stensel8 wants to merge 4 commits into
mainfrom
claude/repo-audit-modernization-7byt68
Open

Fix deployment bugs, rework hardening, add opt-in BitLocker and WinUtil steps#48
Stensel8 wants to merge 4 commits into
mainfrom
claude/repo-audit-modernization-7byt68

Conversation

@Stensel8

@Stensel8 Stensel8 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

Audit of the deployment scripts. Several bugs break a zero-touch run; the hardening baseline did too much in one place and too little in another. Adds BitLocker behind a Y/N prompt and an optional WinUtil tweak step.

The three that matter most:

  • Docs/autounattend.xml never launched WinDeploy. The first-logon script was generated as unattend-02.cmd but contained PowerShell, which cmd.exe cannot run, so Option 1 in the README was broken end to end.
  • BitLocker created no recovery key. Only a TPM protector was added, while the script printed "Make sure to export your BitLocker recovery key!" for a key that never existed. After a TPM clear or mainboard swap the drive was unrecoverable.
  • The hardening set SMB2 = 0, which disables SMB2 and SMB3 and breaks file and printer sharing.

Also fixed: two Set-StrictMode crashes ($null.Count, $LASTEXITCODE before it is set, which also leaked between steps), screen lock written to the deployment account's HKCU with an empty SCRNSAVE.EXE, winget install missing --silent, Office installing interactively, updates without a KB number skipped, seven mistyped winget error codes, HP detection matching "Sharp", HPCMSL installed without bootstrapping NuGet/PSGallery, and Remove-Bloat logging to the wrong directory. Full list in the CHANGELOG.

New: Apply-Tweaks.ps1 runs a WinUtil preset after a Y/N prompt, in its own process. Hardening extended with LSA protection, WDigest, SMB signing, LLMNR, HVCI and 9 Defender ASR rules. Both prompts default to No after 90s; -NonInteractive skips them and is passed through by autounattend.xml.

Type of change

  • Bug fix
  • New feature / script
  • Refactor / cleanup
  • CI / workflow change
  • Documentation

Testing

  • Tested on Windows 11 25H2 — not done, see Notes
  • PSScriptAnalyzer passes locally
  • No hardcoded IPs, credentials, or company-specific data

Verified with PowerShell 7.5.4 and PSScriptAnalyzer 1.24: all 18 scripts parse clean, 0 findings, both validate.yml jobs reproduced locally, autounattend.xml well-formed with CRLF preserved.

Notes

Needs a real 25H2 machine before merge. Nothing was executed against Windows — BitLocker, TPM, Sysprep, winget, Defender ASR and the registry writes are unexercised.

Worth checking while testing:

  • Windows Script Host stays disabled and hardening runs before app installation. A few legacy MSIs use VBScript custom actions and can fail because of it.
  • HVCI and LSA protection need a restart and can surface driver incompatibilities on older hardware.
  • The RMM step now runs inline instead of inside a Start-Job that Remove-Job -Force could kill.
  • The recovery key lands in the deployment account's Documents folder.

Three inline # DevSkim: ignore DS187371 suppressions in Harden-Windows.ps1: DevSkim word-matches XTS from its weak-cipher-mode list, so the prose "XTS-AES-256" was reported as three errors. XTS is the mode Microsoft recommends for BitLocker, and any text naming it matches, so it is suppressed with a justification.

Version bumped to v0.8.0.


Generated by Claude Code

Claude and others added 2 commits August 29, 2026 21:40
…il steps

Audit of the deployment scripts turned up several bugs that break a
zero-touch run, plus a hardening baseline that both did too much in one
place and too little in another.

Bugs 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. Made it a .ps1 and corrected the generator URL.
- Harden-Windows.ps1 set SMB2=0, which disables SMB2 *and* SMB3 and
  breaks all file and printer sharing. Removed; replaced with SMB
  signing and guest-logon hardening.
- Test-IntuneEnrollment and Deploy.ps1 both crashed under StrictMode
  ($null.Count, and $LASTEXITCODE before it is ever set). $LASTEXITCODE
  also leaked between steps, marking later steps as failed.
- Screen lock was written to HKCU, which during deployment is the
  deployment account rather than the end user, and SCRNSAVE.EXE was
  empty so the secure lock never triggered. Now machine-wide policy.
- winget installs were missing --silent, and Office used Display
  Level="Full", so both could show UI mid-deployment.
- Windows updates without a KB number (drivers, definitions) were
  silently skipped by the per-KB install loop.
- Seven winget font error codes were typed -1979335xxx, not -1978335xxx.
- HP detection matched "*hp*", which also matches "Sharp"; HPCMSL was
  installed without bootstrapping NuGet/PSGallery so it stalled.
- Remove-Bloat logged to %TEMP% instead of C:\WinDeploy\Logs, used a
  PowerShell 6+ escape in a 5.1 script, and never implemented the
  "prevents reinstall" its header promised.
- Exit prompts now time out instead of blocking unattended runs.

BitLocker previously created only a TPM protector while telling the
operator to "export your BitLocker recovery key" that never existed,
leaving the drive unrecoverable after a TPM clear or mainboard swap. It
now asks Y/N, creates a recovery password, saves it to the operator's
Documents folder and prints it on screen with a warning to store it.

Added Apply-Tweaks.ps1, an opt-in step that applies a ChrisTitusTech
WinUtil preset after a Y/N confirmation, and extended the hardening
baseline with LSA protection, WDigest, SMB signing, LLMNR, HVCI and
Defender ASR rules.

Both prompts default to No after 90s, and -NonInteractive skips them
entirely for the autounattend path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pt5Q3XvjMT6HMdubvkyLMW
…s properly

Smoke-testing the new confirmation prompts turned up two problems in the
code added in the previous commit.

Read-YesNoWithTimeout used Write-Output for its "no console" message.
Write-Output goes to the same stream as the return value, so the caller
got @("...message...", $false) instead of $false. A 2-element array is
truthy, so `if (-not $enableBitLocker)` took the wrong branch and
answering No would have ENABLED BitLocker. Switched to Write-Host, which
does not touch the output stream, and added a test asserting the return
value is a clean [bool].

The non-interactive short-circuit also never fired. [Environment]::
UserInteractive is $true for any process in a user session, including one
with redirected stdin, so the helper fell through to the polling loop and
sat there for the full 90-second timeout instead of returning the default
immediately. Now also checks [Console]::IsInputRedirected, and Deploy.ps1's
Wait-ForExit got the same guard.

While there: the countdown repainted once a second, which fills the
Start.ps1 transcript with 90 redraw lines per prompt. It now repaints
every 5 seconds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pt5Q3XvjMT6HMdubvkyLMW
Copilot AI lite review requested due to automatic review settings August 29, 2026 22:46
Comment thread Scripts/Deployment/Harden-Windows.ps1 Fixed
Comment thread Scripts/Deployment/Harden-Windows.ps1 Fixed
Comment thread Scripts/Deployment/Harden-Windows.ps1 Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

DevSkim reported three errors on this PR, all "A potentially weak cipher
mode of operation was used". Fetched the check-run annotations rather than
guessing: all three are the literal prose string "XTS-AES-256" in a console
message, a summary label and a docs-link key.

DS187371 word-matches XTS from a list of cipher modes. The hyphens in
"XTS-AES-256" form word boundaries, which is why the prose matches while
the actual code (-EncryptionMethod XtsAes256) does not.

The finding is wrong on the merits: XTS is the mode Microsoft recommends
for BitLocker, and it is what this script configures. Rewording cannot fix
it either, since any text naming the mode matches, and dropping the name
would hide information the operator wants. Suppressed inline with a
justification, which is DevSkim's own mechanism for this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pt5Q3XvjMT6HMdubvkyLMW
Copilot AI review requested due to automatic review settings August 30, 2026 00:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pt5Q3XvjMT6HMdubvkyLMW
Copilot AI review requested due to automatic review settings August 30, 2026 16:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants