Script directory for a PowerShell profile. https://blog.lksz.me/keeping-track-of-everything/#-mypsscriptroot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
11 KiB

  1. [CmdletBinding(SupportsShouldProcess)]param(
  2. [switch]$sudo,
  3. [switch]$RemoveOnly,
  4. [switch]$ShowSkipped,
  5. [switch]$Force,
  6. [switch]$GitClone,
  7. [string]$GitURL = 'https://code.lksz.me/lksz/PowerShell_Scripts',
  8. [Parameter(DontShow)]
  9. [string]$MyPSScriptRoot = $MyPSScriptRoot
  10. )
  11. function _Setup {
  12. [CmdletBinding(SupportsShouldProcess)]param(
  13. [switch]$sudo,
  14. [switch]$RemoveOnly,
  15. [switch]$ShowSkipped,
  16. [switch]$Force,
  17. [switch]$GitClone,
  18. [string]$GitURL = 'https://code.lksz.me/lksz/PowerShell_Scripts',
  19. [Parameter(DontShow)]
  20. [string]$MyPSScriptRoot = $MyPSScriptRoot)
  21. #######################################################################
  22. ## ProfileCode function (source) will contain the code to be written
  23. #######################################################################
  24. function ProfileCode_pre_common {
  25. # # If TMUX isn't loaded, start byobu, then exit this script (and current shell)
  26. # if ( -not ( Test-Path env:TMUX ) ) { byobu; exit }
  27. }
  28. #######################################################################
  29. ## common Profile Code - receommended not to change this
  30. ## Code will be loaded from profile.d sub-directory after this.
  31. #######################################################################
  32. function ProfileCode_post_common {
  33. # This loads the personal profile section from the $MyPSScriptRoot/profile.d directory
  34. Get-Command Reload-MyScripts -ErrorAction SilentlyContinue | ForEach-Object { . $_.Name }
  35. }
  36. function ProfileCode_common {
  37. function Get-PowerShellPath {
  38. Get-Process -PID $PID | ForEach-Object { $_.Path,$_.Parent.Path } | Where-Object { $_ -match 'powershell|pwsh' } | Select-Object -First 1
  39. }
  40. function ConvertTo-Base64 {
  41. param([string]$String)
  42. return [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($String))
  43. }
  44. function Invoke-ExpressionEx() {
  45. [CmdletBinding(SupportsShouldProcess)]param(
  46. [switch]$sudo,
  47. [switch]$Force,
  48. [string]$Function,
  49. [Parameter(Position = 1, ValueFromRemainingArguments = $true)]
  50. [string[]]$expr
  51. )
  52. if( $sudo -and -not $(Test-IsAdmin) ) {
  53. $local:tmpFile = $null
  54. if( $Function ) {
  55. $tmpFile = New-TemporaryFile -WhatIf:$false
  56. $tmpFile = Move-Item $tmpFile.FullName "$($tmpFile.FullName).ps1" -PassThru -WhatIf:$false
  57. Export-FunctionSource $Function | Out-File $tmpFile.FullName -WhatIf:$false
  58. $expr = @( ".", $tmpFile.FullName, ";", $Function ) + $expr
  59. }
  60. $expr = @(
  61. '$WhatIfPreference', '=', "`$$WhatIfPreference", ';',
  62. '$VerbosePreference', '=', "'$VerbosePreference'", ";",
  63. '$ErrorActionPreference', '=', "'$ErrorActionPreference'", ";",
  64. '$ConfirmPreference', '=', "'$ConfirmPreference'", ";"
  65. ) + $expr
  66. $local:sudo_params = $expr -join ' '
  67. Write-Verbose "Perofrming the following command line via SUDO:`n{$sudo_params}"
  68. $local:base64command = ConvertTo-Base64 $sudo_params
  69. /usr/bin/env sudo $(Get-PowerShellPath) -EncodedCommand $base64command
  70. if( $tmpFile ) { Remove-Item $tmpFile }
  71. return
  72. }
  73. Write-Verbose "Perofrming the following expression in-line:`n{$($expr -join ' ')}"
  74. Invoke-Expression "$expr"
  75. }
  76. function Test-IsAdmin{
  77. [CmdletBinding(ConfirmImpact = 'None')]
  78. [OutputType([bool])]
  79. param ()
  80. switch($true){
  81. $( ($PSVersionTable.PSEdition -eq 'Desktop') -or
  82. ($PSVersionTable.Platform -eq 'Win32NT')
  83. ) {
  84. # Fastest way on Windows
  85. ([Security.Principal.WindowsPrincipal](
  86. [Security.Principal.WindowsIdentity]::GetCurrent()
  87. )).IsInRole(
  88. [Security.Principal.WindowsBuiltInRole]'Administrator'
  89. )
  90. }
  91. $( ($PSVersionTable.PSEdition -eq 'Core') -and
  92. ($PSVersionTable.Platform -eq 'Unix')
  93. ) {
  94. # On macOS and Linux we use ID to figure out if we run elevated (0 means superuser rights)
  95. return $((id -u) -eq 0)
  96. }
  97. default {
  98. # Unable to figure it out!
  99. Write-Warning -Message 'Unknown'
  100. return
  101. }
  102. }
  103. }
  104. function Export-FunctionSource {
  105. param(
  106. [switch]$NoHeader,
  107. [Parameter(Position = 0, ValueFromRemainingArguments = $true)]
  108. #[ValidateSet([Functions])]
  109. [string[]]$FunctionName
  110. )
  111. $local:src = ""
  112. foreach( $local:func in $FunctionName ) {
  113. if( -not $NoHeader ) { $src += "`nfunction $func {" }
  114. $src += "`n"
  115. $src += $((Get-Command -Type Function $func).Definition) ### .Replace(('$'+'_'),('$$'+'_'))
  116. if( -not $NoHeader ) { $src += "`n}" }
  117. $src += "`n"
  118. }
  119. return $src.Trim()
  120. }
  121. # Are we running in Unix or Windows?
  122. $global:PathEnvDelimiter = $(if( $PSVersionTable.Platform -match 'unix' ) {':'} else {';'})
  123. function Split-PathEnv {
  124. param([string]$EnvPath)
  125. $EnvPath -split $PathEnvDelimiter
  126. }
  127. # Establish Module Path
  128. $global:MyPSModulePath = Split-PathEnv $env:PSModulePath | Where-Object { $_ -match "^$($(Resolve-Path ~) -replace '\\',"\\")" }
  129. if( -not $MyPSModulePath ) {
  130. $MyPSModulePath = $(Join-Path $(Join-Path $(Resolve-Path ~) 'powershell') 'Modules')
  131. $env:PSModulePath = "$MyPSModulePath$PathEnvDelimiter$env:PSModulePath"
  132. }
  133. if( -not (Test-Path $MyPSModulePath) ) {
  134. New-Item -ItemType Directory -Path $MyPSModulePath -Force | Out-Null
  135. }
  136. Write-Verbose $(Get-Item $MyPSModulePath | Select-Object -ExpandProperty FullName)
  137. # Establish local/personal Script Root and make sure it's in $env:Path
  138. $local:p = Split-PathEnv $env:PATH
  139. $MyPSScriptRoot = Join-Path (Split-Path -Parent $MyPSModulePath) Scripts
  140. Write-Verbose "MyPSScriptRoot = $MyPSScriptRoot"
  141. if( -not (Test-Path $MyPSScriptRoot) ) {
  142. New-Item -ItemType Directory -Path $MyPSScriptRoot -Force | Out-Null
  143. New-Item -ItemType Directory -Path $MyPSScriptRoot/profile.d -Force | Out-Null
  144. }
  145. $global:MyPSScriptRoot = $MyPSScriptRoot
  146. $p = @($p[0], $MyPSScriptRoot) + $($p | Select-Object -Skip 1)
  147. $env:PATH = $p -join $PathEnvDelimiter
  148. }
  149. #######################################################################
  150. ## Setup-Profile code continues below
  151. #######################################################################
  152. if ( -not $MyPSScriptRoot -or (Test-Path function:Test-IsAdmin,function:ConvertTo-Base64,function:Invoke-ExpressionEx,function:Get-PowerShellPath,function:Export-FunctionSource | Where-Object { -not $_ } | Measure-Object | Select-Object -ExpandProperty Count) ) {
  153. Write-Verbose "Calling ProfileCode inline..."
  154. . ProfileCode_common
  155. }
  156. if ( $GitClone -and -not (Test-Path $(Join-Path $MyPSScriptRoot '.git')) ) {
  157. if( -not [bool]$(Get-Command git -ErrorAction SilentlyContinue) ) {
  158. throw "No git command found, you may either omit the -GitClone switch or install git and try again."
  159. }
  160. if( $PSCmdlet.ShouldProcess("Pull git repo from $GitURL into $MyPSScriptRoot ?") ) {
  161. git clone $GitURL $MyPSScriptRoot
  162. }
  163. }
  164. # if( $sudo -and -not $(Test-IsAdmin) ) {
  165. # $local:sudo_args = @('-MyPSScriptRoot',$MyPSScriptRoot)
  166. # $ScriptPSBoundParams.Keys | Where-Object {
  167. # ($ScriptPSBoundParams[$_] -is [Switch]) -and $ScriptPSBoundParams[$_] -and ($_ -notin ('sudo','GitClone','GitURL'))
  168. # } | ForEach-Object { $sudo_args += "-$_" }
  169. #
  170. # return Invoke-ExpressionEx -sudo -Function 'Setup-ProfileInternal' $sudo_args
  171. # }
  172. $local:ProfileSignature = [PSCustomObject]([ordered]@{
  173. Begin = '#### SZ Auto Profile Setup - BEGIN ####'
  174. End = '#### SZ Auto Profile Setup - END ####'
  175. })
  176. function New-ProfileSetupStatus{
  177. param($Name,$Path,$Status,$Exist)
  178. return [PSCustomObject]([ordered]@{
  179. Name = $Name
  180. Path = $Path
  181. Status = $Status
  182. Exist = $Exist
  183. })
  184. }
  185. $local:_profiles= $PROFILE | fl * -Force | Out-String -Stream | ForEach-Object {
  186. $local:p = $($_ -split '( : /)|(:\\)');
  187. if( $p[0] -match 'User' ) {
  188. $p[0].Trim()
  189. }
  190. }
  191. $local:_profile = $profile.CurrentUserAllHosts;
  192. $local:randomSeed = "####$(Get-Random)####";
  193. $local:written = $false;
  194. if( $RemoveOnly -and $ConfirmPreference -eq 'High' -and (-not $PSBoundParameters -or -not $PSBoundParameters.ContainsKey('Confirm')) ) {
  195. $script:ConfirmPreference = 'Low'
  196. }
  197. if( $sudo -or $(Test-IsAdmin) ) { $_profile = $profile.AllUsersAllHosts }
  198. $_profiles | Foreach-Object {
  199. $local:p = Invoke-Expression "`$profile.$_"
  200. $local:profileContent = $randomSeed;
  201. $local:exists = Test-Path $p
  202. $local:userFile = $($_ -match 'CurrentUser')
  203. $local:cleanupValue = ''
  204. if ( $exists -and ((Get-Content -Path $p | Out-String ) -match $ProfileSignature.Begin ) ) {
  205. Write-Verbose "Found code in [$_]'$p', preparing..."
  206. $local:gate = $true;
  207. $profileContent = Get-Content $p | ForEach-Object {
  208. if( -not $gate ) {
  209. if ( $_ -match $ProfileSignature.End ) {
  210. $gate = $true;
  211. }
  212. } else {
  213. if ( $_ -match $ProfileSignature.Begin ) {
  214. $gate = $false;
  215. $cleanupValue = ' '
  216. return $randomSeed;
  217. }
  218. return $_
  219. }
  220. }
  221. }
  222. if( ($p -eq $_profile) -and -not $RemoveOnly ) {
  223. Write-Verbose "Inserting ProfileCode into $($_)..."
  224. $profileContent = $profileContent -replace $randomSeed,$((@(
  225. '',
  226. $ProfileSignature.Begin,
  227. '',
  228. $(Export-FunctionSource -NoHeader ProfileCode_pre_common),
  229. '',
  230. '',
  231. $(Export-FunctionSource -NoHeader ProfileCode_common),
  232. '',
  233. '',
  234. $(Export-FunctionSource -NoHeader ProfileCode_post_common),
  235. '',
  236. $ProfileSignature.End
  237. ) -join "`n" ).Trim().Replace(('$'+'_'),('$$'+'_')))
  238. }
  239. $profileContent = $($profileContent -replace $randomSeed,$cleanupValue).Trim()
  240. $local:shouldSudo = $sudo -and -not $userFile
  241. $local:status = 'Skipped'
  242. if( $VerbosePreference -eq 'Continue' ) { $ShowSkipped = $true }
  243. if( -not ([string]::IsNullOrWhiteSpace($profileContent)) ) {
  244. $status = "Need $(if($cleanupValue){'Setup'}else{'Cleanup'})"
  245. if( $sudo -or $userFile ) {
  246. Write-Verbose "Writing content to $($_)..."
  247. $local:tmpOutput = New-TemporaryFile -WhatIf:$false
  248. $profileContent | Out-File $tmpOutput.FullName -WhatIf:$false
  249. if( $shouldSudo -and $( $PSVersionTable.Platform -eq 'Unix' ) ) {
  250. Invoke-ExpressionEx -sudo:$shouldSudo chmod 'a+r' $tmpOutput.FullName
  251. }
  252. try {
  253. $local:errMsg = $(Invoke-ExpressionEx -sudo:$shouldSudo "Move-Item $($tmpOutput.FullName) $p -Force:`$$Force -ErrorAction Stop | Out-Null" 2>&1)
  254. if( $errMsg ) {
  255. Write-Error "$errMsg"
  256. $status = $status -replace 'Need ','Failed '
  257. } else {
  258. $status = $status -replace 'Need ',''
  259. }
  260. } catch {
  261. Write-Error "Writing $p failed!"
  262. }
  263. } else {
  264. $status = "Need $status"
  265. Write-Error "Cannot write into [$_]'$p'! Please re-run with sudo."
  266. }
  267. } elseif( ($Force -or $RemoveOnly) -and $exists ) {
  268. $status = 'Need Removal'
  269. if( $sudo -or $userFile ) {
  270. Write-Verbose "Removing [$_]'$p'..."
  271. try {
  272. Invoke-ExpressionEx -sudo:$shouldSudo Remove-Item $p
  273. if( -not (Test-Path $p) ) { $status = 'Removed' }
  274. } catch {
  275. Write-Error "Removal of $p failed!"
  276. }
  277. } else {
  278. Write-Error "Cannot remove [$_]'$p'! Please re-run with sudo."
  279. }
  280. }
  281. if( $status -ne 'Skipped' -or $ShowSkipped ) {
  282. if( $shouldSudo -and $status -ne 'Skipped' ) { $status = "$status via sudo" }
  283. New-ProfileSetupStatus -Name $_ -Path $p -Status $status -Exist $(Test-Path $p)
  284. }
  285. }
  286. Remove-Item function:ProfileCode* -Confirm:$false
  287. }
  288. if( -not $SetupFromWeb ) {
  289. _setup @PSBoundParameters
  290. Get-Item function:_setup | Remove-Item
  291. }
  292. Remove-Variable SetupFromWeb