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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
#requires -Version 5.1
<#
.SYNOPSIS
Installs cpsc and its HTML manual for the current user.
.DESCRIPTION
Copies cpsc.exe, doc\cpsc.html and doc\language HTML pages, creates a help shortcut, and
adds the installation directory to the user PATH. No administrator is needed
for the default location: %LOCALAPPDATA%\Programs\cpsc.
.EXAMPLE
.\install.ps1 -SourceDir .\build
.EXAMPLE
.\install.ps1 -Prefix 'D:\My Programs\cpsc'
#>
[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$Prefix,
[ValidateNotNullOrEmpty()]
[string]$SourceDir = $PSScriptRoot,
[switch]$Help
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
if ($Help) {
Write-Output 'Usage: install.ps1 [-Prefix DIR] [-SourceDir DIR] [-Help]'
Write-Output 'Default destination: %LOCALAPPDATA%\Programs\cpsc'
Write-Output 'Source must contain cpsc.exe, doc\cpsc.html and doc\language HTML pages; default: script directory.'
return
}
function Test-PathEntry([string]$PathValue, [string]$Directory) {
$key = $Directory.Replace('/', '\').TrimEnd('\')
foreach ($entry in ($PathValue -split ';')) {
$expanded = [Environment]::ExpandEnvironmentVariables($entry.Trim().Trim('"'))
if ($expanded.Replace('/', '\').TrimEnd('\') -ieq $key) {
return $true
}
}
return $false
}
try {
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
throw 'This installer requires Windows.'
}
if (-not [string]::IsNullOrEmpty($env:DESTDIR)) {
throw 'DESTDIR staging is unsupported on Windows; use -Prefix instead.'
}
if (-not $PSBoundParameters.ContainsKey('Prefix')) {
$localAppData = [Environment]::GetFolderPath('LocalApplicationData')
if ([string]::IsNullOrWhiteSpace($localAppData)) {
throw 'Cannot locate the current user application directory; specify -Prefix.'
}
$Prefix = Join-Path $localAppData 'Programs\cpsc'
}
if ($Prefix -notmatch '^(?:[A-Za-z]:[\\/]|\\\\[^\\]+\\[^\\]+)' -or $Prefix.Contains(';')) {
throw '-Prefix must be an absolute Windows path without a semicolon.'
}
$Prefix = [IO.Path]::GetFullPath($Prefix)
if (-not (Test-Path -LiteralPath $SourceDir -PathType Container)) {
throw "Cannot open source directory: $SourceDir"
}
$SourceDir = (Resolve-Path -LiteralPath $SourceDir).ProviderPath
$sourceExe = Join-Path $SourceDir 'cpsc.exe'
$sourceManual = Join-Path $SourceDir 'doc\cpsc.html'
foreach ($source in @($sourceExe, $sourceManual)) {
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) {
throw "Missing source file: $source; build cpsc and the doc target first."
}
}
$sourceLanguageDir = Join-Path $SourceDir 'doc\language'
if (-not (Test-Path -LiteralPath $sourceLanguageDir -PathType Container)) {
throw "Missing language manuals: $sourceLanguageDir; build the doc target first."
}
$sourceOverview = Join-Path $sourceLanguageDir 'cpsc-language-overview.html'
if (-not (Test-Path -LiteralPath $sourceOverview -PathType Leaf)) {
throw "Missing language overview: $sourceOverview; build the doc target first."
}
$languageManuals = @(Get-Item -LiteralPath $sourceOverview)
foreach ($category in @('function', 'keyword')) {
$categoryDir = Join-Path $sourceLanguageDir $category
$categoryManuals = @(Get-ChildItem -LiteralPath $categoryDir -Filter "cpsc-$category-*.html" -File)
if ($categoryManuals.Count -eq 0) {
throw "Missing language manuals: $categoryDir; build the doc target first."
}
$languageManuals += $categoryManuals
}
if ($languageManuals.Count -eq 0) {
throw "Missing language manuals: $sourceLanguageDir; build the doc target first."
}
$docDir = Join-Path $Prefix 'doc'
$languageDir = Join-Path $docDir 'language'
$targetExe = Join-Path $Prefix 'cpsc.exe'
$targetManual = Join-Path $docDir 'cpsc.html'
foreach ($directory in @($Prefix, $docDir, $languageDir, (Join-Path $languageDir 'function'), (Join-Path $languageDir 'keyword'))) {
if ((Test-Path -LiteralPath $directory) -and
-not (Test-Path -LiteralPath $directory -PathType Container)) {
throw "Installation directory is occupied by a file: $directory"
}
}
$targetFiles = @($targetExe, $targetManual) + @($languageManuals | ForEach-Object {
Join-Path $languageDir ($_.FullName.Substring($sourceLanguageDir.Length + 1))
})
foreach ($file in $targetFiles) {
if (Test-Path -LiteralPath $file -PathType Container) {
throw "Installation file is occupied by a directory: $file"
}
}
$programs = [Environment]::GetFolderPath('Programs')
if ([string]::IsNullOrWhiteSpace($programs)) {
throw 'Cannot locate the current user Start Menu.'
}
$shortcutDir = Join-Path $programs 'cpsc'
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
$shell = New-Object -ComObject WScript.Shell
# Directory.CreateDirectory treats paths literally, including spaces and brackets.
foreach ($category in @('function', 'keyword')) {
[IO.Directory]::CreateDirectory((Join-Path $languageDir $category)) | Out-Null
}
if ($sourceExe -ine $targetExe) {
Copy-Item -LiteralPath $sourceExe -Destination $targetExe -Force
}
if ($sourceManual -ine $targetManual) {
Copy-Item -LiteralPath $sourceManual -Destination $targetManual -Force
}
foreach ($manual in $languageManuals) {
$destination = Join-Path $languageDir ($manual.FullName.Substring($sourceLanguageDir.Length + 1))
if ($manual.FullName -ine $destination) {
Copy-Item -LiteralPath $manual.FullName -Destination $destination -Force
}
}
[IO.Directory]::CreateDirectory($shortcutDir) | Out-Null
$shortcut = $shell.CreateShortcut((Join-Path $shortcutDir 'cpsc Help.lnk'))
$shortcut.TargetPath = $targetManual
$shortcut.WorkingDirectory = $docDir
$shortcut.Description = 'cpsc command-line manual'
$shortcut.Save()
# Read only the user PATH; never persist the combined process/system PATH.
if (-not (Test-PathEntry $userPath $Prefix)) {
$separator = if ([string]::IsNullOrEmpty($userPath) -or $userPath.EndsWith(';')) { '' } else { ';' }
[Environment]::SetEnvironmentVariable('Path', "$userPath$separator$Prefix", 'User')
}
if (-not (Test-PathEntry $env:PATH $Prefix)) {
$env:PATH = "$env:PATH;$Prefix"
}
Write-Output "Installed $targetExe"
Write-Output "Installed $targetManual and $($languageManuals.Count) language manuals in $languageDir"
Write-Output 'Open the manual from Start Menu > cpsc > cpsc Help.'
Write-Output 'Open a new terminal to use cpsc by name (existing parent terminals keep their old PATH).'
}
catch {
Write-Error "install.ps1: $($_.Exception.Message) Check the destination permissions and supplied paths." -ErrorAction Continue
exit 1
}
|