# Remediation logic

This page describes the overall remediation logic and the primary remediation actions executed by the script.

This page is a concise, single-reference summary of the remediation flow implemented by Win-Storage-Remediate.ps1. It intentionally links to the helper functions in the script for implementation details; prefer calling the helpers from tests rather than reimplementing enumerators or retry logic.

# Table of contents

  • Quick reference: where to find the code
    • Helper anchors
      • Write-CustomMessage
      • Get-RecycleBinSize
      • Invoke-DISM-Safe
      • Invoke-RemoveWithRetry
  • Storage Sense Cleanup
  • DISM Component Store Cleanup
  • User-Specific Temporary File Cleanup
  • Event Log Cleanup
  • Old User Profile Cleanup
  • Logging
  • Disk Space Accounting
  • Notes for testers and automation

# Quick reference: where to find the code

If you want to jump straight to the implementation in the script, the primary helpers and regions are:

  • Write-CustomMessage — centralised log-first message writer (writes to the per-run temp log and emits host output only in verbose mode).
  • Merge-Log — consolidates the temporary per-run log into the main log file and implements a fast-path when SKIP_SLOW_IO is set plus fallback archival behaviour.
  • Get-RecycleBinSize — COM-first probe with per-user $Recycle.Bin fallback; returns a stable numeric value (int64) or 0 on failure.
  • Invoke-DISM-Safe and Wait-ForDISM — DISM wrapper and helper that implement pending-reboot gating and ShouldProcess support.
  • Invoke-UserTempCleanup, Invoke-GlobalTempCleanup, Invoke-RemoveWithRetry, Get-SafeChildItems — safe enumerators and removal helpers used for per-user temp and global temp cleanup.
  • Event-log helpers (wevtutil wrapper) — robust wevtutil epl/wevtutil cl wrapper with quoting fallbacks for problematic channel names.
  • Region comment: "Region 2A - Remove old user profiles" — contains the logic that enumerates and removes old user profiles based on $userProfileRetentionDays.

These helpers are implemented inline in intune/remediation/Win-Storage-Remediate.ps1 and include detailed comment-blocks describing behaviour and edge cases. Refer to the function names above to locate the code quickly.

# Helper anchors

The following short headings provide stable fragment IDs so you can link directly to key helpers in the document or other pages.

# Write-CustomMessage

See the Write-CustomMessage helper in the script for the log-first behaviour and how the temporary per-run log is written. Use this ID to link directly to the function description.

# Get-RecycleBinSize

See the Get-RecycleBinSize helper for details about COM-first probing and the conservative per-user fallback.

# Invoke-DISM-Safe

See Invoke-DISM-Safe for the DISM wrapper behaviour, pending-reboot gating and ShouldProcess support.

# Invoke-RemoveWithRetry

See Invoke-RemoveWithRetry for path canonicalisation, protection checks and retry/backoff behaviour.

# Storage Sense Cleanup

  • Configures and triggers Storage Sense to remove temporary files and locally cached OneDrive content where supported.
  • Uses Set-StorageSense and Start-StorageSense when those cmdlets are available; otherwise the script logs that Storage Sense is not configurable on the host.

Safety notes: configuration is applied only when supported and the effective configuration and results are written to the temporary per-run log.

# DISM Component Store Cleanup

  • Runs DISM with /Online /Cleanup-Image /StartComponentCleanup to reduce WinSxS component store size.
  • The DISM wrapper functions support ShouldProcess, so callers can use -WhatIf/-Confirm for non-destructive dry-runs.
  • Pending reboot behaviour: the script detects a pending reboot and will skip DISM unless forced with -ForceDISMWhenPending or $Pref_ForceDISMWhenPendingOverride = $true.

Precedence note: the wrapper (Invoke-DISM-Safe) checks for the command-line switch -ForceDISMWhenPending (when the script is invoked with that parameter) and the file-level preference $Pref_ForceDISMWhenPendingOverride. Either will force a DISM run when a pending reboot is detected; the wrapper evaluates both.

Safety notes: DISM can be long-running and may need elevation; exit codes and timing are logged to the per-run log. The DISM wrapper is declared with SupportsShouldProcess so callers may use -WhatIf/-Confirm for safe dry-runs.

# User-Specific Temporary File Cleanup

  • Enumerates user profiles under C:\Users with safe enumerators and removes per-user temporary files (for example %LOCALAPPDATA%\Temp, browser caches when requested, and other safe targets).
  • The script protects system and special folders and will not operate on protected paths; helpers perform path canonicalisation and protection checks.

Privacy and host output:

  • Per-profile deletion details (file paths and per-user actions) are written only to the per-run temporary log. The host/Intune output intentionally emits a compact aggregated summary to avoid leaking PII. Example host message: User profile cleanup completed. RemovedCount=3; FailedCount=0.

Note: the script intentionally emits only that compact summary text to the host (Intune captures host output); detailed per-file and per-profile diagnostics are preserved only in the per-run temp log.

Performance option:

  • SKIP_SLOW_IO is propagated into the environment (as SKIP_SLOW_IO) to enable fast-path behaviour for CI/tests (reduces retries/backoff and skips deep slow enumerations).

SKIP_SLOW_IO and Merge-Log behaviour:

  • When SKIP_SLOW_IO is set the script sets the environment variable SKIP_SLOW_IO=1. Helpers consult this environment value to avoid slow enumerations and blocking I/O.
  • Merge-Log implements a fast-path when SKIP_SLOW_IO is present. In fast-path mode Merge-Log simulates a successful merge for tests/CI to avoid blocking on file locks. If Merge-Log cannot merge in normal runs it will attempt a timestamped copy to the archive directory and write a diagnostic file — see the Merge-Log helper for details.

# Event Log Cleanup

  • Exports selected event logs to an archive folder ($env:TEMP\EventLogArchives) using wevtutil epl and clears logs with wevtutil cl after successful export.
  • Safety notes: logs are cleared only after successful export; failures are retained and logged to the per-run log.

Implementation note: the event-log helper calls the Windows wevtutil binary directly and includes fallbacks when wevtutil epl returns a parameter error for certain channel names — it retries via cmd /c with quoted args. This makes the export/clear step more robust on channels that contain spaces or special characters.

# Old User Profile Cleanup

  • Removes user profiles under C:\Users that are not whitelisted (Default, Default User, Public, All Users) and whose last use is older than $userProfileRetentionDays days.
  • The deletion process uses safe checks, retries and canonical path validation. Per-profile success/failure is written to the per-run temporary log; the host receives only an aggregated summary.

Implementation note: profile removal is implemented in the script region labelled Region 2A - Remove old user profiles. The region checks a whitelist (for example Default, Default User, Public, All Users) and skips protected system profiles.

# Logging

  • The script uses a "log-first" approach: full run details (including per-profile entries) are appended to a per-run temporary log (%TEMP%\<scriptName>_temp.log).
  • Host output is kept minimal (compact summaries and essential progress) to avoid PII exposure and to keep Intune-captured output concise.
  • At run end the temporary log is consolidated into the main archive and a per-run archive is created.

Merge-Log fallback behaviour:

  • Merge-Log will attempt multiple read/write retries with exponential backoff to handle locked temp logs.
  • If a direct merge fails, the helper falls back to copying the temp log to a timestamped file in the archive directory and writes a short diagnostic file describing the merge failure. This ensures per-run data is never silently lost.

Inspecting logs:

  • For troubleshooting or tests, run the script with -DryRun -Verbosity Verbose and inspect the temporary per-run log in %TEMP% for full details.

Verbosity note:

  • Default behaviour is -Verbosity Normal. When -Verbosity Verbose is used the script sets $VerbosePreference = 'Continue' and Write-CustomMessage also emits verbose host output (helpful for interactive debugging). In Normal mode, host output is compact and per-file details remain in the temp log.

# Disk Space Accounting

  • Captures initial free space on the target volume (typically C:) and final free space after remediation; recovered bytes are calculated and recorded.
  • Recycle Bin estimation: the script attempts a COM (Shell.Application) query first, and falls back to per-user Recycle.Bin enumeration when COM is not available; results are normalised to an integer value.

Caveat: the per-user Recycle.Bin enumeration fallback is intentionally conservative and may under-report the true bin size in some hosts (for example when access is denied or when running as SYSTEM). When the COM path is available it tends to be more accurate.

# Notes for testers and automation

  • Default testing: prefer -DryRun to validate actions without making changes.
  • Use -CleanMgrOnly to test CleanMgr preparation behaviour.
  • Use -SKIP_SLOW_IO in CI or slow VMs to avoid long enumeration passes.
  • To see full per-profile details, run with -DryRun -Verbosity Verbose and open the temporary per-run log file.

Testing notes (Pester guidance):

  • Prefer -DryRun for initial tests to avoid destructive changes.
  • Use -SKIP_SLOW_IO in CI or on slow VMs to speed up tests and exercise the fast-path behaviours.
  • For DISM-related tests, consume Invoke-DISM-Safe as a unit under test and make use of its ShouldProcess semantics (-WhatIf) where appropriate.
  • Tests that need to assert per-profile details should read the per-run temp log file in %TEMP% rather than relying on host output.

Where to look in the code

  • Primary script: intune/remediation/Win-Storage-Remediate.ps1 (helper function comments are the canonical source of behaviour and edge-case handling).