How Windows finds programs, why “not recognized” happens, and how to install software the programmer’s way with winget.
$env:PATH — GUI and PowerShellwingetYou type python. What actually happens?
python a PowerShell alias, function, or cmdlet?PATH, in orderpython.exe, .cmd, .bat, .ps1…Nothing found in any folder?python: The term 'python' is not recognized as a name of a cmdlet, function, script file, or executable program.
PowerShell does not look in the current folder. To run build.ps1 sitting right here, type .\build.ps1.
Named text values that every program inherits when it starts — shared settings for the whole session.
PS> $env:USERNAME
Jane
PS> $env:USERPROFILE
C:\Users\Jane
PS> ls env: # list them all
| Variable | Typical value |
|---|---|
USERPROFILE | C:\Users\Jane |
APPDATA | …\AppData\Roaming |
LOCALAPPDATA | …\AppData\Local |
TEMP | …\AppData\Local\Temp |
COMPUTERNAME | LAPTOP-7F3K |
PATH | the list of program folders |
Syntax differs by shell: $env:TEMP in PowerShell, %TEMP% in cmd & Explorer, os.environ["TEMP"] in Python.
PATH: the phone book for programsOne long string of folders separated by ;
Windows reads it top to bottom and runs the first match.
Split it into lines to make it readable:$env:PATH -split ';'
PS> $env:PATH -split ';'
C:\Program Files\PowerShell\7
C:\WINDOWS\system32
C:\WINDOWS
C:\WINDOWS\System32\WindowsPowerShell\v1.0\
C:\Program Files\Git\cmd
C:\Users\Jane\.local\bin
C:\Users\Jane\AppData\Local\Microsoft\WindowsApps
C:\Users\Jane\AppData\Local\Programs\Microsoft VS Code\bin
pythonFresh Windows installs contain a stub python.exe in WindowsApps that just opens the Microsoft Store.
Ask PowerShell which one wins:
PS> Get-Command python -All | Select-Object Source
Source
------
C:\Users\Jane\AppData\Local\Microsoft\WindowsApps\python.exe
where.exe python does the same job and works in cmd too.
Get-Command answers the question “which program will actually run?” — the first thing to check whenever a tool behaves strangely.
Turn the stub off: Settings → Apps → Advanced app settings → App execution aliases → switch off python.exe and python3.exe. In Module 4, uv will give us real Pythons.
Each program copies the environment when it starts. After you change a variable permanently:
Changed PATH? Close and reopen the terminal — every time.
| Set with… | Lasts |
|---|---|
$env:X = "…" | This terminal window only — gone when closed |
| Environment Variables dialog | Permanent, all future programs |
[Environment]::SetEnvironmentVariable(…, "User") | Permanent, all future programs |
Path → Edit…Shortcuts that open the same dialog:
rundll32 sysdm.cpl,EditEnvironmentVariables
sysdm.cpl → Advanced tab → Environment Variables… (this route shows System variables too, which need admin to change)
Never paste over the whole Path value. Add or remove one entry at a time.
# Read
$env:PATH -split ';'
[Environment]::GetEnvironmentVariable("Path", "User") -split ';' # just the User part
# Temporary — this window only (great for experiments)
$env:PATH = "C:\actuarial_work\bin;" + $env:PATH
$env:MY_DATA_DIR = "C:\actuarial_work\data"
# Permanent — append a folder to the USER Path
$bin = "C:\actuarial_work\bin"
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
[Environment]::SetEnvironmentVariable("Path", "$userPath;$bin", "User")
The permanent version writes to the registry, exactly like the GUI. Run it once; running it twice adds the folder twice (harmless, but messy).
.exe on disk (installer docs, winget list).C:\Users\Jane\.local\bin.$env:PATH -split ';' — is that folder listed?Path and repeat step 5.PS> uv --version
uv: The term 'uv' is not recognized…
PS> Test-Path ~\.local\bin\uv.exe
True
PS> $env:PATH -split ';' |
>> Select-String local
(nothing)
Diagnosis: installed, but its folder isn’t on PATH in this window.
| Extension | What it is | Run by |
|---|---|---|
.exe | Compiled program — python.exe, git.exe, uv.exe | Windows directly |
.cmd / .bat | Batch script — old-style command list | cmd.exe |
.ps1 | PowerShell script | PowerShell — if execution policy allows |
.py | Python script | Python: python script.py / uv run script.py |
The variable PATHEXT lists which extensions Windows tries automatically: $env:PATHEXT → .COM;.EXE;.BAT;.CMD;…
.ps1 won’t runPS> .\mkproj.ps1
.\mkproj.ps1 cannot be loaded because running
scripts is disabled on this system.
Windows client PCs default to Restricted. The recommended setting for developers:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
Get-ExecutionPolicy -List # verify
| Policy | Meaning |
|---|---|
| Restricted | No scripts at all |
| RemoteSigned | Your own scripts run; downloaded scripts must be signed |
| Unrestricted | Everything runs (warns on downloads) |
| Bypass | No checks — used for one-off commands |
-Scope CurrentUser = no admin needed, affects only you. On managed work PCs, IT policy may override this.
Point-and-click installers vs. the winget package manager.
.exe / .msi)winget)winget upgrade --all updates everythingEither way, most installers edit your PATH. That’s why this lives in the PATH module — and why you open a new terminal after every install.
wingetwinget search pandoc # find packages
winget show --id JohnMacFarlane.Pandoc # details
winget install --id JohnMacFarlane.Pandoc -e
winget list # what's installed
winget upgrade # what's out of date
winget upgrade --all # update everything
winget uninstall --id JohnMacFarlane.Pandoc
Microsoft.PowerShell |
astral-sh.uv |
Git.Git |
JohnMacFarlane.Pandoc |
Microsoft.VisualStudioCode |
--id … -e means “this exact ID”, avoiding look-alike packages. The first run may ask you to accept the source agreements — read, then type Y.
Goal: type mkproj pricing_2026 from any folder and get a ready-made project skeleton.
C:\actuarial_work\binmkproj.ps1PATHmkdir C:\actuarial_work\bin
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
notepad C:\actuarial_work\bin\mkproj.ps1 # say "Yes" to create the file
Type this into Notepad and save:
# mkproj.ps1 — create a standard actuarial project skeleton
param([Parameter(Mandatory)] [string] $Name)
$folders = "data", "notebooks", "src", "output", "tests"
foreach ($f in $folders) {
New-Item -ItemType Directory -Force -Path (Join-Path $Name $f) | Out-Null
}
Write-Host "Created $Name with: $($folders -join ', ')" -ForegroundColor Green
Check with ls C:\actuarial_work\bin that it’s mkproj.ps1 — not mkproj.ps1.txt (Module 1 flashback!).
GUI: Edit environment variables for your account → Path → New → C:\actuarial_work\bin
PowerShell:
$bin = "C:\actuarial_work\bin"
$u = [Environment]::GetEnvironmentVariable("Path","User")
[Environment]::SetEnvironmentVariable("Path","$u;$bin","User")
PS C:\Users\Jane> cd C:\actuarial_work
PS C:\actuarial_work> mkproj pricing_2026
Created pricing_2026 with: data, notebooks,
src, output, tests
PS C:\actuarial_work> Get-Command mkproj
CommandType Name Source
----------- ---- ------
ExternalScript mkproj.ps1 C:\actuarial_work\bin\mkproj.ps1
Bonus: install something with winget (e.g. winget install --id JohnMacFarlane.Pandoc -e — we need it in Module 6), then find which PATH entry it added.
$env:NAMEPATH one entry per linePATH or installing software, I open a new terminalGet-Command and Test-PathRemoteSigned for CurrentUserwingetmkproj works from any folderPython itself, virtual environments, and reproducible packages — installed per-user, no admin, no PATH surgery.