fix(install): don't crash the PowerShell installer when $PROFILE is unset (#2469)

## Description

The PowerShell installer (`scripts/install.ps1`) crashes at the very end
on any machine where PowerShell cannot resolve the current user's
profile path.

`Ensure-ProfileBlock` locates the profile with:

```powershell
$profileDir = Split-Path -Parent $PROFILE
```

`$PROFILE` is an empty string when PowerShell cannot compute the profile
path for the current user, which happens for a fresh account with no
Documents folder yet, a service or CI context, or a redirected profile.
`Split-Path -Parent ''` then throws:

```
Split-Path : Cannot bind argument to parameter 'Path' because it is an empty string.
```

Because the script runs under `$ErrorActionPreference = 'Stop'`, that
terminates the whole installer with a non-zero exit, even though it
happens after the `headroom` wrapper and the persistent User PATH entry
were already written. The user sees a scary Split-Path error and assumes
the install failed.

## Fix

Skip the profile convenience block when `$PROFILE` is empty and log why.
`Ensure-PathEntry` already persists the User PATH for new sessions, so
the only thing skipped is auto-refreshing PATH inside the current
profile file, which does not exist in that environment anyway.
Well-behaved environments with a real `$PROFILE` are unchanged.

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `scripts/install.ps1`: early-return from `Ensure-ProfileBlock` with an
informational message when `$PROFILE` is null or empty, before the
`Split-Path` call.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [ ] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
$ python -m pytest tests/test_install/test_native_installers.py -q
1 passed, 1 skipped

# The PowerShell lifecycle test was failing on main before this change and now passes:
$ python -m pytest "tests/test_install/test_native_installers.py::test_powershell_native_installer_supports_persistent_docker_lifecycle" -q
1 passed
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12, Windows PowerShell 5.1, project
venv (`uv sync --extra proxy`), pytest in the venv.
- Exact command / steps: ran `install.ps1` under a temp `USERPROFILE`
with no Documents folder (the same setup the installer test uses).
Confirmed `$PROFILE` resolves to an empty string in that context and
that `Split-Path -Parent $PROFILE` throws there, then re-ran the
installer test with the fix.
- Observed result: before the fix the installer aborted with `Split-Path
: Cannot bind argument to parameter 'Path' because it is an empty
string` and exit code 1 (and the test failed); after the fix the
installer completes, writes the wrapper and PATH entry, logs that it
skipped the profile update, and the test passes. Ran against the actual
script.
- Not tested: a real end-user account whose Documents folder is
redirected to a network share.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
This commit is contained in:
Abhay Singh
2026-08-12 10:42:30 +05:30
committed by GitHub
parent e583e082d8
commit fc5c4e239c
+13
View File
@@ -36,6 +36,19 @@ function Ensure-PathEntry {
function Ensure-ProfileBlock {
param([string]$PathEntry)
# $PROFILE is empty when PowerShell cannot resolve the profile path for the
# current user (a fresh account with no Documents folder, a service/CI
# context, a redirected profile). Split-Path below would then throw
# "Cannot bind argument to parameter 'Path' because it is an empty string",
# and with $ErrorActionPreference = 'Stop' that aborts the whole installer
# AFTER the wrapper and persistent User PATH were already written. Skip the
# profile convenience block instead: Ensure-PathEntry has already persisted
# the PATH for new sessions.
if ([string]::IsNullOrEmpty($PROFILE)) {
Write-Info 'Skipping PowerShell profile update: $PROFILE is not set in this environment (PATH was still updated for new sessions).'
return
}
$markerStart = '# >>> headroom docker-native >>>'
$markerEnd = '# <<< headroom docker-native <<<'
$escapedPathEntry = $PathEntry.Replace("'", "''")