Остання активність 1748532636

albert's Avatar albert ревизій цього gist 1748532636. До ревизії

1 file changed, 396 insertions

Setup-CoreutilsLinks.ps1(файл створено)

@@ -0,0 +1,396 @@
1 + <#
2 + .SYNOPSIS
3 + Automates the setup of uutils/coreutils on Windows by creating batch file shims
4 + for its commands and assisting with PATH configuration.
5 +
6 + .DESCRIPTION
7 + This script attempts to locate an existing uutils/coreutils installation.
8 + If not found, it can optionally install it via winget.
9 + It then dynamically fetches the list of available utilities from coreutils.exe
10 + and creates batch file shims (e.g., ls.bat, cat.bat) in a specified directory.
11 + These shims call the main coreutils.exe with the appropriate command.
12 + Finally, it can help add this shims directory to the User's PATH environment variable.
13 +
14 + .PARAMETER ShimsDirectory
15 + The directory where the batch file shims for coreutils commands will be created.
16 + Defaults to "$env:LOCALAPPDATA\uutils-shims".
17 +
18 + .PARAMETER ForceInstall
19 + If specified, and coreutils is not found, the script will attempt to install
20 + it via winget without prompting for confirmation.
21 +
22 + .PARAMETER ForcePathAddition
23 + If specified, and the ShimsDirectory is not in the PATH, the script will
24 + attempt to add it to the User PATH without prompting for confirmation.
25 +
26 + .PARAMETER SkipPathCheck
27 + If specified, the script will not check or offer to modify the PATH environment variable.
28 +
29 + .EXAMPLE
30 + .\Setup-CoreutilsShims.ps1
31 + Runs the script with default settings and interactive prompts.
32 +
33 + .EXAMPLE
34 + .\Setup-CoreutilsShims.ps1 -ShimsDirectory "C:\myCLItools" -ForceInstall -ForcePathAddition
35 + Runs the script, creates shims in "C:\myCLItools", and attempts to install
36 + coreutils and add the directory to PATH without prompting.
37 +
38 + .EXAMPLE
39 + .\Setup-CoreutilsShims.ps1 -Verbose
40 + Runs the script with detailed verbose output.
41 +
42 + .NOTES
43 + - Changes to the PATH environment variable typically require restarting terminal
44 + sessions or logging out and back in to take effect.
45 + - If you want coreutils commands (like 'dir', 'sort') to override built-in
46 + Windows commands, the ShimsDirectory may need to be placed *before*
47 + C:\Windows\System32 in your PATH environment variable. This script adds
48 + it to the end of the User PATH by default if modification is chosen.
49 + #>
50 + [CmdletBinding(SupportsShouldProcess = $true)]
51 + param(
52 + [string]$ShimsDirectory = (Join-Path $env:LOCALAPPDATA "uutils-shims"),
53 +
54 + [switch]$ForceInstall,
55 +
56 + [switch]$ForcePathAddition,
57 +
58 + [switch]$SkipPathCheck
59 + )
60 +
61 + # Script-level error action preference
62 + $ErrorActionPreference = "Stop" # Stop on terminating errors
63 +
64 + # --- Helper Functions ---
65 + function Show-Menu ([string]$Title, [string]$PromptMessage) {
66 + Write-Host "`n--- $Title ---"
67 + $options = @(
68 + [System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Proceed with this action.")
69 + [System.Management.Automation.Host.ChoiceDescription]::new("&No", "Skip this action.")
70 + )
71 + $decision = $host.UI.PromptForChoice($Title, $PromptMessage, $options, 0)
72 + return ($decision -eq 0) # 0 is Yes
73 + }
74 +
75 + # --- Core Logic Functions ---
76 +
77 + function Find-CoreutilsExecutable {
78 + Write-Verbose "Attempting to find coreutils.exe..."
79 +
80 + # 1. Try Get-Command (if already in PATH or an alias exists)
81 + Write-Verbose "Checking PATH for coreutils..."
82 + $coreutilsCmd = Get-Command coreutils -ErrorAction SilentlyContinue
83 + if ($coreutilsCmd -and $coreutilsCmd.Source -and (Test-Path $coreutilsCmd.Source -PathType Leaf)) {
84 + Write-Host "Found coreutils via Get-Command at: $($coreutilsCmd.Source)" -ForegroundColor Green
85 + return $coreutilsCmd.Source
86 + }
87 +
88 + # 2. Try known winget installation path pattern
89 + Write-Verbose "Checking common winget installation paths..."
90 + $wingetPackagesBase = Join-Path $env:LOCALAPPDATA "Microsoft\WinGet\Packages"
91 + $coreutilsPackageDirPattern = "uutils.coreutils_Microsoft.Winget.Source_8wekyb3d8bbwe" # Common package identifier
92 +
93 + if (Test-Path $wingetPackagesBase) {
94 + $packageInstallDir = Get-ChildItem -Path $wingetPackagesBase -Directory -Filter $coreutilsPackageDirPattern -ErrorAction SilentlyContinue |
95 + Select-Object -First 1
96 + if ($packageInstallDir) {
97 + Write-Verbose "Found potential coreutils package directory: $($packageInstallDir.FullName)"
98 + # Find the latest versioned subdirectory (often contains version string or arch)
99 + $latestVersionDir = Get-ChildItem -Path $packageInstallDir.FullName -Directory -ErrorAction SilentlyContinue |
100 + Sort-Object -Property Name -Descending | # Sort by name, hoping newer versions list first
101 + Select-Object -First 1
102 + if ($latestVersionDir) {
103 + $potentialPath = Join-Path $latestVersionDir.FullName "coreutils.exe"
104 + Write-Verbose "Checking for coreutils.exe in: $potentialPath"
105 + if (Test-Path $potentialPath -PathType Leaf) {
106 + Write-Host "Found coreutils in winget package directory: $potentialPath" -ForegroundColor Green
107 + return $potentialPath
108 + }
109 + } else {
110 + Write-Verbose "No version subdirectories found under $($packageInstallDir.FullName)"
111 + }
112 + } else {
113 + Write-Verbose "Coreutils package directory pattern not found under $wingetPackagesBase"
114 + }
115 + } else {
116 + Write-Verbose "Winget packages base directory not found: $wingetPackagesBase"
117 + }
118 +
119 + # 3. Not found, offer to install via winget
120 + Write-Warning "coreutils.exe not found on this system."
121 + $shouldInstall = $false
122 + if ($ForceInstall) {
123 + $shouldInstall = $true
124 + } else {
125 + if ($PSCmdlet.ShouldProcess("Install uutils/coreutils via winget", "Coreutils not found. Do you want to attempt installation?")) {
126 + $shouldInstall = Show-Menu -Title "Install Coreutils" -PromptMessage "coreutils.exe was not found. Would you like to attempt to install it using winget? (This will run 'winget install uutils.coreutils --source winget -e --accept-package-agreements --accept-source-agreements')"
127 + }
128 + }
129 +
130 + if ($shouldInstall) {
131 + Write-Host "Attempting to install uutils/coreutils via winget..."
132 + $wingetCmd = Get-Command winget -ErrorAction SilentlyContinue
133 + if (-not $wingetCmd) {
134 + Write-Error "winget command not found. Cannot install coreutils automatically."
135 + return $null
136 + }
137 + try {
138 + $installArgs = "install uutils.coreutils --source winget -e --accept-package-agreements --accept-source-agreements"
139 + Write-Verbose "Running: winget $installArgs"
140 + $process = Start-Process winget -ArgumentList $installArgs -Wait -PassThru -WindowStyle Minimized
141 + if ($process.ExitCode -eq 0) {
142 + Write-Host "coreutils installed successfully via winget." -ForegroundColor Green
143 + # Re-attempt find after installation
144 + Write-Verbose "Re-checking for coreutils.exe after installation..."
145 + $coreutilsCmdAfterInstall = Get-Command coreutils -ErrorAction SilentlyContinue
146 + if ($coreutilsCmdAfterInstall -and $coreutilsCmdAfterInstall.Source -and (Test-Path $coreutilsCmdAfterInstall.Source -PathType Leaf)) {
147 + Write-Host "Found coreutils after install at: $($coreutilsCmdAfterInstall.Source)" -ForegroundColor Green
148 + return $coreutilsCmdAfterInstall.Source
149 + }
150 + # If Get-Command doesn't find it immediately, try the winget path again
151 + if (Test-Path $wingetPackagesBase) {
152 + $packageInstallDir = Get-ChildItem -Path $wingetPackagesBase -Directory -Filter $coreutilsPackageDirPattern -ErrorAction SilentlyContinue | Select-Object -First 1
153 + if ($packageInstallDir) {
154 + $latestVersionDir = Get-ChildItem -Path $packageInstallDir.FullName -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
155 + if ($latestVersionDir) {
156 + $potentialPath = Join-Path $latestVersionDir.FullName "coreutils.exe"
157 + if (Test-Path $potentialPath -PathType Leaf) {
158 + Write-Host "Found coreutils in winget package directory after install: $potentialPath" -ForegroundColor Green
159 + return $potentialPath
160 + }
161 + }
162 + }
163 + }
164 + Write-Warning "coreutils installed, but could not immediately locate coreutils.exe. You might need to restart your terminal or find it manually."
165 + return $null
166 + } else {
167 + Write-Error "winget installation failed with exit code $($process.ExitCode)."
168 + return $null
169 + }
170 + } catch {
171 + Write-Error "An error occurred during winget installation: $($_.Exception.Message)"
172 + return $null
173 + }
174 + }
175 + Write-Error "coreutils.exe could not be found or installed."
176 + return $null
177 + }
178 +
179 + function Get-CoreutilsCommands ([string]$CoreutilsExePath) {
180 + Write-Verbose "Attempting to get command list from '$CoreutilsExePath --list'..."
181 + $Commands = $null
182 + try {
183 + $processInfo = New-Object System.Diagnostics.ProcessStartInfo
184 + $processInfo.FileName = $CoreutilsExePath
185 + $processInfo.Arguments = "--list"
186 + $processInfo.RedirectStandardOutput = $true
187 + $processInfo.RedirectStandardError = $true
188 + $processInfo.UseShellExecute = $false
189 + $processInfo.CreateNoWindow = $true
190 +
191 + $process = New-Object System.Diagnostics.Process
192 + $process.StartInfo = $processInfo
193 + $process.Start() | Out-Null
194 +
195 + $output = $process.StandardOutput.ReadToEnd()
196 + $errors = $process.StandardError.ReadToEnd()
197 + $process.WaitForExit()
198 + $exitCode = $process.ExitCode
199 +
200 + if ($exitCode -ne 0 -or [string]::IsNullOrWhiteSpace($output)) {
201 + Write-Warning "Command '$CoreutilsExePath --list' failed or returned empty stdout."
202 + Write-Warning "Exit code: $exitCode"
203 + if (-not [string]::IsNullOrWhiteSpace($errors)) {
204 + Write-Warning "Error output from coreutils --list:`n$errors"
205 + }
206 + } else {
207 + Write-Verbose "Raw output from coreutils --list:`n$output"
208 + $CommandLines = $output.Split([System.Environment]::NewLine)
209 + $ProcessingCommands = $false
210 + $CombinedCommandLines = ""
211 +
212 + if ($output -match "Currently defined functions:") {
213 + Write-Verbose "Parsing based on 'Currently defined functions:' marker."
214 + foreach ($Line in $CommandLines) {
215 + if ($ProcessingCommands) {
216 + $TrimmedLine = $Line.Trim()
217 + if (-not [string]::IsNullOrWhiteSpace($TrimmedLine)) {
218 + $CombinedCommandLines += $TrimmedLine
219 + }
220 + }
221 + if ($Line -match "Currently defined functions:") {
222 + $ProcessingCommands = $true
223 + }
224 + }
225 + if (-not [string]::IsNullOrWhiteSpace($CombinedCommandLines)) {
226 + $Commands = $CombinedCommandLines -split ',' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
227 + }
228 + } else {
229 + Write-Verbose "Attempting to parse as one command per line (fallback)."
230 + $Commands = $CommandLines | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -notmatch "^Usage:" -and $_ -notmatch "^Options:" -and $_ -notmatch "^Currently defined functions:" -and $_ -notmatch "multi-call binary" }
231 + }
232 +
233 + if ($Commands -and $Commands.Length -gt 0) {
234 + Write-Host "Successfully parsed $($Commands.Length) commands dynamically from coreutils --list." -ForegroundColor Green
235 + } else {
236 + Write-Warning "Dynamically parsed 0 commands. The output format might have changed or was unexpected."
237 + }
238 + }
239 + } catch {
240 + Write-Warning "An error occurred while trying to execute or parse '$CoreutilsExePath --list': $($_.Exception.Message)"
241 + }
242 +
243 + if (-not $Commands -or $Commands.Length -eq 0) {
244 + Write-Warning "Dynamic command fetching failed or yielded no commands. Using a predefined fallback list."
245 + $Commands = @(
246 + "arch", "b2sum", "b3sum", "base32", "base64", "basename", "basenc", "cat", "cksum", "comm", "cp",
247 + "csplit", "cut", "date", "dd", "df", "dir", "dircolors", "dirname", "du", "echo", "env",
248 + "expand", "expr", "factor", "false", "fmt", "fold", "hashsum", "head", "hostname", "join",
249 + "link", "ln", "ls", "md5sum", "mkdir", "mktemp", "more", "mv", "nl", "nproc", "numfmt",
250 + "od", "paste", "pr", "printenv", "printf", "ptx", "pwd", "readlink", "realpath", "rm",
251 + "rmdir", "seq", "sha1sum", "sha224sum", "sha256sum", "sha3-224sum", "sha3-256sum",
252 + "sha3-384sum", "sha3-512sum", "sha384sum", "sha3sum", "sha512sum", "shake128sum",
253 + "shake256sum", "shred", "shuf", "sleep", "sort", "split", "sum", "sync", "tac", "tail",
254 + "tee", "test", "touch", "tr", "true", "truncate", "tsort", "uname", "unexpand", "uniq",
255 + "unlink", "vdir", "wc", "whoami", "yes", "["
256 + )
257 + Write-Host "Using fallback list with $($Commands.Length) commands."
258 + }
259 + return $Commands
260 + }
261 +
262 + function Create-UtilityShims ([string]$CoreutilsExePath, [array]$Commands, [string]$TargetShimsDirectory) {
263 + Write-Host "`n--- Creating Utility Batch File Shims ---"
264 + Write-Host "Coreutils Executable: $CoreutilsExePath"
265 + Write-Host "Shims Directory: $TargetShimsDirectory"
266 +
267 + if (-not (Test-Path $TargetShimsDirectory)) {
268 + Write-Verbose "Shims directory '$TargetShimsDirectory' does not exist. Attempting to create it."
269 + if ($PSCmdlet.ShouldProcess("Create directory '$TargetShimsDirectory'")) {
270 + try {
271 + New-Item -ItemType Directory -Path $TargetShimsDirectory -Force | Out-Null
272 + Write-Host "Created directory '$TargetShimsDirectory'" -ForegroundColor Green
273 + } catch {
274 + Write-Error "Failed to create directory '$TargetShimsDirectory': $($_.Exception.Message)"
275 + return
276 + }
277 + } else {
278 + Write-Warning "Directory creation skipped by user. Cannot create shims."
279 + return
280 + }
281 + }
282 +
283 + Write-Host "Creating batch file shims..."
284 + $CreatedCount = 0
285 + $SkippedCount = 0
286 + $FailedCount = 0
287 +
288 + foreach ($CommandNameInList in $Commands) {
289 + $ShimmedCommandName = $CommandNameInList # This is the command to pass to coreutils.exe
290 + $BatchFileName = "$CommandNameInList.bat"
291 +
292 + if ($CommandNameInList -eq "[") {
293 + $BatchFileName = "bracket.bat" # Use a valid filename for the '[' command
294 + # $ShimmedCommandName is already "[" which is correct
295 + }
296 +
297 + $BatchPath = Join-Path $TargetShimsDirectory $BatchFileName
298 +
299 + if (Test-Path $BatchPath) {
300 + Write-Verbose "Skipping: Batch file '$BatchPath' already exists."
301 + $SkippedCount++
302 + continue
303 + }
304 +
305 + # Content of the batch file: @echo off, then "path/to/coreutils.exe" command %*
306 + # Ensure $CoreutilsExePath is quoted in case it has spaces.
307 + # $ShimmedCommandName is the command name (e.g., "ls", "uname", "[")
308 + $BatchContent = "@echo off`r`n`"$CoreutilsExePath`" $ShimmedCommandName %*"
309 +
310 + if ($PSCmdlet.ShouldProcess("Create batch file shim '$BatchPath' for command '$ShimmedCommandName'")) {
311 + try {
312 + Set-Content -Path $BatchPath -Value $BatchContent -Encoding Ascii -Force
313 + Write-Verbose "Successfully created batch file: $BatchPath"
314 + $CreatedCount++
315 + } catch {
316 + Write-Warning "Failed to create batch file '$BatchPath': $($_.Exception.Message)"
317 + $FailedCount++
318 + }
319 + } else {
320 + Write-Verbose "Skipped creating batch file '$BatchPath' due to ShouldProcess."
321 + $SkippedCount++
322 + }
323 + }
324 + Write-Host "Shim creation summary: $CreatedCount created, $SkippedCount skipped, $FailedCount failed." -ForegroundColor Cyan
325 + }
326 +
327 + function Manage-PathEnvironmentVariable ([string]$DirectoryToAdd) {
328 + Write-Host "`n--- PATH Environment Variable Check ---"
329 + $currentUserPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
330 + $pathParts = $currentUserPath -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
331 +
332 + if ($pathParts -contains $DirectoryToAdd) {
333 + Write-Host "Directory '$DirectoryToAdd' is already in your User PATH." -ForegroundColor Green
334 + return
335 + }
336 +
337 + Write-Warning "Directory '$DirectoryToAdd' is NOT in your User PATH."
338 + $shouldAddToPath = $false
339 + if ($ForcePathAddition) {
340 + $shouldAddToPath = $true
341 + } else {
342 + if ($PSCmdlet.ShouldProcess("Add '$DirectoryToAdd' to User PATH", "This will make the commands available in new terminal sessions.")) {
343 + $shouldAddToPath = Show-Menu -Title "Update User PATH" -PromptMessage "Would you like to add '$DirectoryToAdd' to your User PATH environment variable?"
344 + }
345 + }
346 +
347 + if ($shouldAddToPath) {
348 + if ($PSCmdlet.ShouldProcess("Set User PATH = '$currentUserPath;$DirectoryToAdd'")) {
349 + try {
350 + $newPath = ($pathParts + $DirectoryToAdd) -join ';'
351 + [System.Environment]::SetEnvironmentVariable("Path", $newPath, "User")
352 + Write-Host "Successfully added '$DirectoryToAdd' to your User PATH." -ForegroundColor Green
353 + Write-Host "IMPORTANT: You need to RESTART your terminal (PowerShell, Command Prompt, etc.) or log out and back in for this change to take full effect." -ForegroundColor Yellow
354 + Write-Host "Note: For coreutils commands like 'dir' or 'sort' to override built-in Windows commands, '$DirectoryToAdd' may need to be placed *before* C:\Windows\System32 in your PATH. This script added it to the end of your User PATH." -ForegroundColor Yellow
355 + } catch {
356 + Write-Error "Failed to add '$DirectoryToAdd' to User PATH: $($_.Exception.Message)"
357 + }
358 + } else {
359 + Write-Warning "PATH modification skipped by user (ShouldProcess)."
360 + }
361 + } else {
362 + Write-Host "Skipped adding '$DirectoryToAdd' to User PATH."
363 + }
364 + }
365 +
366 + # --- Main Script Execution ---
367 + Write-Host "Starting uutils/coreutils Batch Shim Setup Script..." -ForegroundColor Magenta
368 +
369 + # Step 1: Find Coreutils Executable
370 + $coreutilsExe = Find-CoreutilsExecutable
371 + if (-not $coreutilsExe) {
372 + Write-Error "Could not locate or install coreutils.exe. Script cannot continue."
373 + exit 1
374 + }
375 + Write-Host "Using coreutils at: $coreutilsExe" -ForegroundColor Cyan
376 +
377 + # Step 2: Get Command List
378 + $commandsToShim = Get-CoreutilsCommands -CoreutilsExePath $coreutilsExe
379 + if (-not $commandsToShim -or $commandsToShim.Length -eq 0) {
380 + Write-Error "Could not determine the list of coreutils commands. Script cannot continue."
381 + exit 1
382 + }
383 +
384 + # Step 3: Create Utility Shims
385 + Create-UtilityShims -CoreutilsExePath $coreutilsExe -Commands $commandsToShim -TargetShimsDirectory $ShimsDirectory
386 +
387 + # Step 4: Manage PATH (unless skipped)
388 + if (-not $SkipPathCheck) {
389 + Manage-PathEnvironmentVariable -DirectoryToAdd $ShimsDirectory
390 + } else {
391 + Write-Host "`nSkipping PATH environment variable check as per -SkipPathCheck parameter."
392 + }
393 +
394 + Write-Host "`n--- Script Finished ---" -ForegroundColor Magenta
395 + Write-Host "If PATH was modified, remember to restart your terminal sessions."
396 + Write-Host "You can now try running commands like 'ls', 'cat', 'uname', 'bracket ... ]', etc."
Новіше Пізніше