forked from stcu/SharedScripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Optimize-Path.ps1
127 lines (118 loc) · 5.2 KB
/
Optimize-Path.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<#
.SYNOPSIS
Sorts, prunes, and normalizes both user and system Path entries.
#>
#Requires -Version 3
[CmdletBinding(ConfirmImpact='High',SupportsShouldProcess=$true)][OutputType([void])] Param(
<#
Look for commands with the same name within multiple Path entries, and move the entry
with the newest version ahead of the others.
#>
[switch]$ResolveConflicts
)
function Initialize-PathCollections
{
# initialize collections
$Script:app,$Script:user = @{},@()
# get command extensions
$Script:pathext = $env:PATHEXT -split ';'
# get list of directory environment variables
$Script:evmatch = Get-ChildItem env: |
? {Test-Path $_.Value -PathType Container} |
? {'windir','TMP','ProgramW6432','CommonProgramW6432','SystemDrive','HomeDrive','HomePath' -inotcontains $_.Name} |
% {$_.Value = '^' + ($_.Value.Trim('\') -replace '(\W)','\$1') + '(?=\\|$)'; $_} |
sort @{e={$_.Value.Length};asc=$false},@{e={$_.Name.Length};asc=$true}
}
function Backup-Path([Parameter(Position=0,Mandatory=$true)][EnvironmentVariableTarget]$Target)
{
mkdir $env:LOCALAPPDATA\OptimizePath |Out-Null
"$(Get-Date -Format u)`t$($Target)`t$([Environment]::GetEnvironmentVariable('Path',$Target))" |
Add-Content -Path $env:LOCALAPPDATA\OptimizePath\Backup.tsv
}
filter Get-PathDetail([Parameter(Position=0,Mandatory=$true)][EnvironmentVariableTarget]$Target,
[Parameter(Position=1,ValueFromPipeline=$true)][string]$Entry)
{
if([string]::IsNullOrWhiteSpace($Entry)) {return}
$fullpath = [Environment]::ExpandEnvironmentVariables($Entry)
Write-Verbose "$Target Path: $Entry$(if($Entry -ne $fullpath){' ('+$fullpath+')'})"
if(!(Test-Path $fullpath -PathType Container)) {Write-Warning "$Target Path: Entry $Entry not found!"; return}
if($Target -ne 'User' -and $entry.StartsWith("$env:USERPROFILE\")) {Write-Warning "$Target Path: Entry $entry under user profile!"; $user+= $entry; continue}
[IO.FileInfo[]]$cmd = Get-ChildItem $fullpath -File |? {$pathext -icontains $_.Extension}
if(!$cmd) {Write-Warning "$Target Path: Entry $Entry contains no executables."; return}
elseif($cmd.Count -eq 1) {Write-Warning "$Target Path: Entry $Entry contains only one command! ($($cmd[0].Name))"}
else {Write-Verbose "$Target Path: Entry $Entry contains $($cmd.Count) commands."}
foreach($c in $cmd)
{
if($app[$c.Name]) {$app[$c.Name]+=$Entry}
else {[string[]]$app[$c.Name] = $Entry}
}
$ev = $evmatch |? {$Entry -match $_.Value} |select -First 1
if($ev) {Write-Verbose "$Target Path: $entry matches $($ev.Name) /$($ev.Value)/"}
$evpath =
if($Entry -like '%*') {$Entry}
elseif(!$ev) {$fullpath}
else {$Entry -ireplace $ev.Value,"%$($ev.Name)%"}
return [pscustomobject]@{
Name = $Entry
Path = $fullpath
EnvVarPath = $evpath
Commands = $cmd
Precede = [string[]]@()
}
}
function Get-PathDetails([Parameter(Position=0,Mandatory=$true)][EnvironmentVariableTarget]$Target)
{
$path = [Environment]::GetEnvironmentVariable('Path',$Target) -split ';'
switch($Target)
{
Machine {$path |Get-PathDetail $Target}
User {($Script:user + $path) |Get-PathDetail $Target}
}
}
function Resolve-PathConflicts([Parameter(Position=0,Mandatory=$true)][EnvironmentVariableTarget]$Target,
[Parameter(Position=1,Mandatory=$true)][Collections.Generic.List[psobject]]$PathDetails)
{
# examine conflicts
if(!(gcm -Verb Test -Noun NewerFile)) {Set-Alias Test-NewerFile "$PSScriptRoot\Test-NewerFile.ps1"}
foreach($c in $app.Keys |? {$app.$_.Count -gt 1})
{
$newest,$rest = $app[$c]
[string[]]$precede = @()
foreach($r in $rest)
{
if(Test-NewerFile (Join-Path $newest $c) (Join-Path $r $c)) {$precede += $newest; $newest = $r}
}
if($precede)
{
Write-Verbose "Executable conflict ${c}: $newest needs to be moved ahead of $($precede -join ', ')."
$npos,$rpos = 0,0
foreach($i in 0..($precede.Length))
{
if($PathDetails[$i].Name -eq $newest) {$npos = $i}
elseif($rpos -ne 0 -and $precede -contains $PathDetails[$i].Name) {$rpos = $i}
}
if($npos -gt $rpos)
{
$n = $PathDetails[$npos]
$PathDetails.RemoveAt($npos)
$PathDetails.Insert($rpos,$n)
}
}
}
return $PathDetails
}
function Update-Path([Parameter(Position=0,Mandatory=$true)][EnvironmentVariableTarget]$Target)
{
[Collections.Generic.List[psobject]]$path = Get-PathDetails $Target
if($ResolveConflicts) {$path = Resolve-PathConflicts $path}
if($PSCmdlet.ShouldProcess("$Target Path",'Update'))
{
$newpath = ($path |select -Unique -ExpandProperty EnvVarPath) -join ';'
Write-Verbose "New $Target Path: $newpath"
Backup-Path $Target
[Environment]::SetEnvironmentVariable('Path',$newpath,$Target)
}
}
Initialize-PathCollections
Update-Path Machine
Update-Path User