Skip to content

Commit b40cd6f

Browse files
committed
Initial commit
0 parents  commit b40cd6f

7 files changed

Lines changed: 321 additions & 0 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CI - Pathloader checks
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
check-path:
9+
runs-on: windows-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Run path checker
13+
shell: pwsh
14+
run: |
15+
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
16+
./scripts/check_user_path.ps1

‎.gitignore‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Ignore generated artifacts and temp files
2+
*_set_user_path_tmp.ps1
3+
out.vcd
4+
*.wlf
5+
*.vcd
6+
*.log
7+
8+
# Editor and OS files
9+
.vscode/
10+
.idea/
11+
*.swp
12+
.DS_Store
13+
Thumbs.db

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Pathloader ? Windows 사용자 PATH(HKCU) 관리 유틸리티
2+
3+
간단한 설명
4+
Pathloader는 Windows에서 사용자 수준(HKCU)의 PATH 환경변수를 쉽게 관리하도록 돕는 작은 스크립트 모음입니다.
5+
`scripts/list.txt`에 도구의 bin 경로를 적고, 체크 스크립트로 누락 여부를 확인한 뒤 배치 파일로 누락된 항목을 사용자 PATH에 추가하는 흐름으로 설계되어 있습니다.
6+
7+
주요 파일
8+
- `scripts/check_user_path.ps1` ? `list.txt`에 명시된 경로들이 사용자 PATH에 있는지 검사합니다.
9+
- `scripts/set_user_path.bat` ? `list.txt` 또는 명령행 인자로 전달된 세미콜론(;) 구분 경로들을 사용자 PATH에 선행(prepend)으로 추가합니다.
10+
- `scripts/list.txt` ? 예시 경로 목록(한 줄에 하나, `#`으로 주석 처리 가능).
11+
- `.github/workflows/ci.yml` ? (옵션) Windows runner에서 체크 스크립트를 실행하도록 구성된 예시 워크플로.
12+
- `LICENSE`, `.gitignore` ? 라이선스와 git 무시 규칙.
13+
14+
빠른 시작 (Quick start)
15+
1) 저장소 가져오기
16+
- 이 폴더를 원하는 위치로 복사하거나 GitHub에서 클론하세요. 예: `D:\Pathloader`
17+
18+
2) 경로 목록 편집
19+
- `scripts/list.txt`에 추가하려는 도구의 bin 디렉터리를 한 줄에 하나씩 적습니다.
20+
예: `C:\msys64\mingw64\bin` 또는 `C:\ProgramData\chocolatey\bin`
21+
22+
3) 검사 (PowerShell)
23+
- PowerShell에서(저장소 루트):
24+
25+
```powershell
26+
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
27+
.\scripts\check_user_path.ps1
28+
```
29+
30+
- 스크립트는 모든 항목이 존재하면 종료코드 0, 일부 누락이면 1, `list.txt`가 없거나 비어있으면 2를 반환합니다.
31+
32+
4) 누락 항목 추가
33+
- `set_user_path.bat`는 두 가지 방식으로 동작합니다:
34+
- `scripts/list.txt`가 존재하면 그 내용을 사용
35+
- 존재하지 않으면 배치 파일의 첫 번째 인자에서 세미콜론(;)으로 구분된 경로 문자열을 사용
36+
37+
- 예 (명령 프롬프트):
38+
39+
```bat
40+
cd D:\Pathloader\scripts
41+
set_user_path.bat "C:\custom\bin;C:\another\bin"
42+
```
43+
44+
- 예 (PowerShell):
45+
46+
```powershell
47+
cd D:\Pathloader\scripts
48+
.\n+# 또는 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass & .\set_user_path.bat "C:\custom\bin;C:\another\bin"
49+
```
50+
51+
5) 변경 확인
52+
- 새 PowerShell 창을 열고 `$env:Path` 또는 `Get-ChildItem env:Path`로 반영 여부를 확인하세요.
53+
54+
안전 및 주의사항
55+
- 권한: 이 스크립트는 사용자 수준(HKCU)을 수정하므로 관리자 권한이 필요하지 않습니다.
56+
- 실행 정책: `set_user_path.bat`는 내부적으로 임시 PowerShell 스크립트를 생성하고 `-ExecutionPolicy Bypass`로 실행합니다. 조직의 보안정책에 따라 제한될 수 있습니다.
57+
- 임시 파일 충돌 예방: 배치 파일은 `%TEMP%`에 임시 PS 스크립트를 생성합니다. 동일한 이름의 충돌 가능성을 줄이기 위해(예: 프로세스 ID 또는 타임스탬프를 포함한 이름 사용)을 권장합니다. (현재 배치파일에서는 `_set_user_path_tmp.ps1`을 사용합니다.)
58+
- 백업 권장: 대규모 변경 전에 현재 사용자 PATH를 백업하세요. PowerShell에서 예: `[Environment]::GetEnvironmentVariable('Path','User') | Out-File user_path_backup.txt`.
59+
- 브로드캐스트 제한: 스크립트는 WM_SETTINGCHANGE를 전송하여 Explorer 등에게 알리지만, 이미 실행 중인 일부 프로그램은 변경을 인식하려면 재시작이 필요할 수 있습니다.
60+
61+
팁 및 확장 제안
62+
- 존재하는 경로만 추가하도록 하는 옵션(예: `--exist-only`) 또는 삽입 방식(append vs prepend) 옵션을 추가하면 안전성과 유연성이 올라갑니다.
63+
- 경로 정규화 개선(심볼릭 링크, NT 경로, 환경변수 확장 등)을 `check_user_path.ps1`에 추가하면 더 강건한 비교가 가능합니다.
64+
65+
CI
66+
- `.github/workflows/ci.yml`는 예시로 Windows runner에서 `check_user_path.ps1`을 실행합니다. CI 환경의 PATH는 로컬과 다를 수 있으므로, 실제 PATH 변경(예: `set_user_path.bat`)은 로컬에서 실행해야 합니다.
67+
68+
라이선스
69+
- 이 프로젝트는 MIT 라이선스로 배포됩니다(파일 `LICENSE` 참고).
70+
71+
피드백 / 기여
72+
- 사용 중 개선점이나 추가 기능(예: 임시 파일명 충돌 제거, `--exist-only` 옵션, 더 정교한 정규화)이 필요하면 이슈나 PR을 열어주세요.
73+
74+
---
75+
English summary (short)
76+
Pathloader provides simple scripts to manage the user PATH on Windows. Edit `scripts/list.txt`, run the checker, and use the batch script to add missing tool paths. See the README for additional notes about usage, safety and suggestions for enhancements.

‎scripts/check_user_path.ps1‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<#
2+
check_user_path.ps1
3+
4+
English: This script reads paths from a list.txt file in the same directory and
5+
verifies whether each path exists in the current user's PATH environment variable (HKCU\Environment).
6+
It returns exit code 0 if all paths are present, 1 if some are missing, and 2 if list.txt is missing or empty.
7+
8+
Korean: 이 스크립트는 동일 디렉터리의 list.txt 파일에서 경로를 읽어와
9+
현재 사용자의 PATH 환경변수(HKCU\Environment)에 해당 경로들이 존재하는지 검사합니다.
10+
모두 존재하면 종료코드 0, 일부 누락이면 1, list.txt가 없거나 비어있으면 2를 반환합니다.
11+
12+
Usage / 사용법:
13+
PowerShell (from workspace folder / 워크스페이스 폴더에서):
14+
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
15+
.\check_user_path.ps1
16+
#>
17+
18+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
19+
$listFile = Join-Path $scriptDir 'list.txt'
20+
21+
if (-not (Test-Path $listFile)) {
22+
Write-Host "list.txt 파일을 찾을 수 없습니다: $listFile" -ForegroundColor Yellow
23+
exit 2
24+
}
25+
26+
# Read lines, ignore blank and commented lines. Normalize for comparison.
27+
# English: load list.txt and ignore blank/comment lines
28+
# Korean: list.txt을 읽어 빈 줄과 주석 라인을 무시합니다
29+
$lines = Get-Content $listFile | ForEach-Object { $_.Trim() } | Where-Object { ($_ -ne '') -and (-not $_.StartsWith('#')) }
30+
if ($lines.Count -eq 0) {
31+
Write-Host "list.txt 에 유효한 경로가 없습니다." -ForegroundColor Yellow
32+
exit 2
33+
}
34+
35+
# Normalize function: trim and remove trailing backslash, use lowercase for comparison
36+
function Normalize-Path([string]$p) {
37+
# English: normalize path for comparison: trim, remove trailing backslash, lowercase
38+
# 한국어: 경로 비교를 위해 정규화합니다(공백제거, 후행 백슬래시 제거, 소문자화)
39+
if ([string]::IsNullOrWhiteSpace($p)) { return '' }
40+
$t = $p.Trim()
41+
# remove trailing backslashes for stable comparison
42+
$t = $t.TrimEnd('\\')
43+
return $t.ToLowerInvariant()
44+
}
45+
46+
$userPathRaw = [Environment]::GetEnvironmentVariable('Path','User')
47+
$userPathList = @()
48+
if (-not [string]::IsNullOrEmpty($userPathRaw)) {
49+
$userPathList = $userPathRaw -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
50+
}
51+
52+
# Build a hashset of normalized user PATH entries for fast, case-insensitive lookup
53+
$userSet = @{}
54+
foreach ($u in $userPathList) { $userSet[(Normalize-Path $u)] = $true }
55+
56+
$allPresent = $true
57+
Write-Host "list.txt에 명시된 경로들이 사용자 PATH에 있는지 확인합니다:`n" -ForegroundColor Cyan
58+
foreach ($p in $lines) {
59+
$norm = Normalize-Path $p
60+
if ($norm -ne '' -and $userSet.ContainsKey($norm)) {
61+
Write-Host "[OK] $p" -ForegroundColor Green
62+
} else {
63+
Write-Host "[MISS] $p" -ForegroundColor Red
64+
$allPresent = $false
65+
}
66+
}
67+
68+
if ($allPresent) {
69+
Write-Host "`n모든 경로가 사용자 PATH에 존재합니다." -ForegroundColor Green
70+
exit 0
71+
} else {
72+
Write-Host "`n일부 경로가 사용자 PATH에 없습니다." -ForegroundColor Yellow
73+
Write-Host "list.txt의 경로들을 추가하려면: .\set_user_path.bat 를 실행하세요." -ForegroundColor Cyan
74+
exit 1
75+
}

‎scripts/list.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# example list.txt - one path per line
2+
C:\msys64\mingw64\bin
3+
C:\ProgramData\chocolatey\bin
4+
C:\Program Files\Git
5+
C:\Program Files\Git\bin
6+
C:\Program Files\GitHub CLI

‎scripts/set_user_path.bat‎

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
@echo off
2+
REM set_user_path.bat
3+
REM English: This batch reads list.txt (if present) or uses the first argument
4+
REM (semicolon-separated paths) and prepends missing entries into the user's PATH (HKCU).
5+
REM Lines starting with '#' or empty lines in list.txt are ignored.
6+
REM 한국어: 이 배치파일은 동일 디렉터리의 list.txt(존재하면)를 읽거나,
7+
REM 명령행 인자로 전달된 세미콜론(;) 구분 경로들을 사용하여,
8+
REM 사용자 환경변수 PATH(HKCU)에 누락된 경로를 중복 없이 선행(prepend)으로 추가합니다.
9+
REM list.txt의 빈 줄과 '#'으로 시작하는 주석은 무시됩니다.
10+
11+
setlocal EnableDelayedExpansion
12+
13+
set "SCRIPT_DIR=%~dp0"
14+
set "LISTFILE=%SCRIPT_DIR%list.txt"
15+
set "ADDPATHS="
16+
set "DRY_RUN=0"
17+
if "%~1"=="--dry-run" (
18+
set "DRY_RUN=1"
19+
shift
20+
)
21+
22+
if exist "%LISTFILE%" (
23+
REM list.txt을 한 줄씩 읽어 빈 줄과 주석을 무시하고 세미콜론 구분 문자열을 구성
24+
REM English: read list.txt line-by-line, ignore blank and comment lines, build semicolon list
25+
for /f "usebackq delims=" %%L in ("%LISTFILE%") do (
26+
set "line=%%L"
27+
REM Trim leading spaces (tokens=* removes leading spaces)
28+
REM English: remove leading whitespace from the line
29+
for /f "tokens=*" %%A in ("!line!") do set "line=%%A"
30+
if not "!line!"=="" (
31+
REM '#'로 시작하는 주석 라인 무시
32+
REM English: ignore lines beginning with '#'
33+
echo !line! | findstr /b "#" >nul
34+
if errorlevel 1 (
35+
REM 파일 시스템에 경로가 존재하지 않으면 경고를 stderr로 출력(그러나 목록에는 포함)
36+
REM English: warn if the listed path does not exist, but still include it
37+
if not exist "!line!\" (
38+
echo 경고: 목록에 명시된 경로가 존재하지 않음: !line! >&2
39+
)
40+
if defined ADDPATHS (
41+
set "ADDPATHS=!ADDPATHS!;!line!"
42+
) else (
43+
set "ADDPATHS=!line!"
44+
)
45+
)
46+
)
47+
)
48+
) else (
49+
if "%~1"=="" (
50+
echo 사용법: %~nx0 ^"C:\path1;C:\path2^"
51+
echo 또는 이 스크립트와 동일한 디렉터리에 한 줄에 하나씩 경로를 적은 list.txt 파일을 생성하세요.
52+
echo English: Usage: %~nx0 "C:\path1;C:\path2"
53+
echo Or create a list.txt file with one path per line in the same directory as this script.
54+
exit /b 1
55+
)
56+
set "ADDPATHS=%~1"
57+
)
58+
59+
REM Prepare temporary PowerShell script to update user PATH safely
60+
REM Use a unique filename to avoid collisions when run in parallel or by multiple users.
61+
REM Construct a sanitized timestamp stamp and include a random component.
62+
set "STAMP=%DATE%_%TIME%"
63+
rem remove characters that are invalid/unfriendly in filenames
64+
set "STAMP=%STAMP::=%"
65+
set "STAMP=%STAMP:.=%"
66+
set "STAMP=%STAMP:/=%"
67+
set "STAMP=%STAMP: =_%"
68+
set "STAMP=%STAMP:,=%"
69+
set "TMP_PS=%TEMP%\_set_user_path_tmp_%STAMP%_%RANDOM%.ps1"
70+
71+
rem Escape single quotes in ADDPATHS for embedding into PS script
72+
set "PS_ADD=%ADDPATHS:'='''%"
73+
74+
if "%DRY_RUN%"=="1" (
75+
echo DRY RUN: temporary PowerShell filename would be:
76+
echo %TMP_PS%
77+
endlocal
78+
exit /b 0
79+
)
80+
81+
>"%TMP_PS%" echo $add = '%PS_ADD%' -split ';' ^| Where-Object {$_ -ne ''}
82+
>>"%TMP_PS%" echo $current_raw = [Environment]::GetEnvironmentVariable('Path','User')
83+
>>"%TMP_PS%" echo if ([string]::IsNullOrEmpty($current_raw)) { $current = @() } else { $current = $current_raw -split ';' ^| Where-Object {$_ -ne ''} }
84+
>>"%TMP_PS%" echo foreach ($p in $add) { if (-not ($current -contains $p)) { $current = ,$p + $current } }
85+
>>"%TMP_PS%" echo $newPath = ($current -join ';')
86+
>>"%TMP_PS%" echo [Environment]::SetEnvironmentVariable('Path',$newPath,'User')
87+
>>"%TMP_PS%" echo Write-Host '사용자 PATH가 업데이트되었습니다 (상위 항목):'
88+
>>"%TMP_PS%" echo $current[0..([math]::Min(9,$current.Count-1))] ^| ForEach-Object { Write-Host " $_" }
89+
>>"%TMP_PS%" echo ""
90+
>>"%TMP_PS%" echo # Broadcast WM_SETTINGCHANGE so Explorer and other apps pick up the change
91+
>>"%TMP_PS%" echo $signature = @"
92+
>>"%TMP_PS%" echo using System;
93+
>>"%TMP_PS%" echo using System.Runtime.InteropServices;
94+
>>"%TMP_PS%" echo public class NativeMethods {
95+
>>"%TMP_PS%" echo [DllImport(^"user32.dll^", CharSet = CharSet.Auto)]
96+
>>"%TMP_PS%" echo public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
97+
>>"%TMP_PS%" echo }
98+
>>"%TMP_PS%" echo "@
99+
>>"%TMP_PS%" echo Add-Type -TypeDefinition $signature -ErrorAction Stop
100+
>>"%TMP_PS%" echo $HWND_BROADCAST = [IntPtr]0xffff
101+
>>"%TMP_PS%" echo $WM_SETTINGCHANGE = 0x001A
102+
>>"%TMP_PS%" echo [UIntPtr]$result = [UIntPtr]::Zero
103+
>>"%TMP_PS%" echo [NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, 'Environment', 0, 5000, [ref]$result) ^| Out-Null
104+
>>"%TMP_PS%" echo Write-Host '환경변수 변경을 브로드캐스트했습니다 (WM_SETTINGCHANGE).' -ForegroundColor Cyan
105+
106+
REM Run the temporary PowerShell script (bypass execution policy for this run)
107+
powershell -NoProfile -ExecutionPolicy Bypass -File "%TMP_PS%"
108+
109+
REM Remove temporary script
110+
del /f /q "%TMP_PS%" >nul 2>&1
111+
112+
endlocal
113+
echo Done.
114+
exit /b 0

0 commit comments

Comments
 (0)