[CmdletBinding(SupportsShouldProcess)]param( [switch]$sudo, [switch]$RemoveOnly, [switch]$ShowSkipped, [switch]$Force, [switch]$NoGitAction, [string]$GitURL = 'https://code.lksz.me/lksz/PowerShell_Scripts', [Parameter(DontShow)] [string]$My_PSScriptRoot = $MyPSScriptRoot ) function _Setup { [CmdletBinding(SupportsShouldProcess)]param( [switch]$sudo, [switch]$RemoveOnly, [switch]$ShowSkipped, [switch]$Force, [switch]$NoGitAction, [string]$GitURL = 'https://code.lksz.me/lksz/PowerShell_Scripts', [Parameter(DontShow)] [string]$My_PSScriptRoot = $MyPSScriptRoot) ####################################################################### ## ProfileCode function (source) will contain the code to be written ####################################################################### function ProfileCode_pre_common { # # If TMUX isn't loaded, start byobu, then exit this script (and current shell) # if ( -not ( Test-Path env:TMUX ) ) { byobu; exit } } ####################################################################### ## common Profile Code - receommended not to change this ## Code will be loaded from profile.d sub-directory after this. ####################################################################### function ProfileCode_post_common { # This loads the personal profile section from the $MyPSScriptRoot/profile.d directory Get-Command Reload-MyScripts -ErrorAction SilentlyContinue | ForEach-Object { . $_.Name } } ####################################################################### ## _Setup Logic starts here ####################################################################### . ProfileCode_common if( -not $MyPSScriptRoot ) { throw '$MyPSScriptRoot does NOT exist!' } if ( -not $NoGitAction ) { if ( -not [bool]$(Get-Command git -ErrorAction SilentlyContinue) ) { throw "No git command found, you may either omit run with the -NoGitAction switch or install git and try again." } Push-Location $MyPSScriptRoot if( Test-Path $(Join-Path $MyPSScriptRoot '.git') ) { git pull } elseif ( $PSCmdlet.ShouldProcess("Pull git repo from $GitURL into $MyPSScriptRoot ?") ) { $local:tmpGitDir = New-TemporaryFile Remove-Item $tmpGitDir New-Item -Type Directory $tmpGitDir.FullName | Out-Null Write-Verbose "Cloning git repo [$GitURL] into temporary location ('$($tmpGitDir.FullName)')..." $local:GitOutput = "$(& git clone $GitURL $tmpGitDir.FullName --no-checkout)" Write-Verbose $GitOutput Write-Verbose "Moving git repo from temp location to $MyPSScriptRoot . . ." Copy-Item -Recurse (Join-Path $tmpGitDir.FullName .git) ./.git Remove-Item $tmpGitDir.FullName Write-Verbose "Checking out repo..." $GitOutput = "$(& git checkout --force)" Write-Verbose $GitOutput } Pop-Location Write-Verbose "Git operation done." } $local:ProfileSignature = [PSCustomObject]([ordered]@{ Begin = '#### SZ Auto Profile Setup - BEGIN ####' End = '#### SZ Auto Profile Setup - END ####' Removed = '#### SZ Auto Profile Setup - ----- ####' Match = '#### SZ Auto Profile Setup' }) function New-ProfileSetupStatus { param($Name, $Path, $Status, $Exist) return [PSCustomObject]([ordered]@{ Name = $Name Path = $Path Status = $Status Exist = $Exist }) } $local:_profiles = $PROFILE | fl * -Force | Out-String -Stream | ForEach-Object { $local:p = $($_ -split ' : [/A-Z\\]'); if ( $p[0] -match 'User' ) { $p[0].Trim() } } $local:_profile = $profile.CurrentUserAllHosts; $local:randomSeed = "####$(Get-Random)####"; if ( $RemoveOnly -and $ConfirmPreference -eq 'High' -and (-not $PSBoundParameters -or -not $PSBoundParameters.ContainsKey('Confirm')) ) { $script:ConfirmPreference = 'Low' } if ( $sudo -or $(Test-IsAdmin) ) { $_profile = $profile.AllUsersAllHosts } $_profiles | Foreach-Object { $local:p = Invoke-Expression "`$profile.$_" $local:profileContent = $randomSeed; $local:exists = Test-Path $p $local:userFile = $($_ -match 'CurrentUser') $local:cleanupValue = '' if ( $exists ) { if ( (Get-Content -Path $p | Out-String ) -match $ProfileSignature.Match ) { Write-Verbose "Found code in [$_]'$p', preparing..." $local:gate = $true; $profileContent = Get-Content $p | ForEach-Object { if ( -not $gate ) { if ( $_ -match $ProfileSignature.End ) { $gate = $true; } } else { if ( $_ -match $ProfileSignature.Match ) { if ( $_ -match $ProfileSignature.Begin ) { $gate = $false; } $cleanupValue = $ProfileSignature.Removed return $randomSeed; } return $_ } } } else { $profileContent += "`n`n$(Get-Content $p -Raw)" } } if( -not( $profileContent -match $randomSeed ) ) { $profileContent = "$randomSeed`n$profileContent" } $local:insertProfileCode = $false; if ( ($p -eq $_profile) -and -not $RemoveOnly ) { $insertProfileCode = $true Write-Verbose "Inserting ProfileCode into $($_)..." $cleanupValue = $((@( '', $ProfileSignature.Begin, '', $(Export-FunctionSource -NoHeader ProfileCode_pre_common), '', '', $(Export-FunctionSource -NoHeader ProfileCode_common), '', '', $(Export-FunctionSource -NoHeader ProfileCode_post_common), '', $ProfileSignature.End ) -join "`n" ).Trim().Replace(('$' + '_'), ('$$' + '_'))) } $profileContent = $($profileContent -replace $randomSeed, $cleanupValue).Trim() $local:shouldSudo = $sudo -and -not $userFile $local:status = 'Skipped' if ( $VerbosePreference -eq 'Continue' ) { $ShowSkipped = $true } # Test if new profile Content will be empty if ( -not ([string]::IsNullOrWhiteSpace($profileContent)) ) { $status = "Need $(if($insertProfileCode){if($exists){'Setup'}else{'Init'}}else{'Cleanup'})" if ( $sudo -or $userFile ) { Write-Verbose "Writing content to $($_)..." $local:tmpOutput = New-TemporaryFile -WhatIf:$false -Confirm:$false $profileContent | Out-File $tmpOutput.FullName -WhatIf:$false -Confirm:$false if ( $shouldSudo -and $( $PSVersionTable.Platform -eq 'Unix' ) ) { # Make sure the file has the right permissions on a Unix machine Invoke-ExpressionEx -sudo:$shouldSudo chmod 'a+r' $tmpOutput.FullName } try { $local:errMsg = $(Invoke-ExpressionEx -sudo:$shouldSudo @" if( -not (Test-Path $(Split-Path -Parent $p)) ) { New-Item -Type Directory $(Split-Path -Parent $p) -Force }; Copy-Item $($tmpOutput.FullName) $p -Force:`$$Force -ErrorAction Stop | Out-Null; Remove-Item $($tmpOutput.FullName) -Force:`$$Force -ErrorAction Stop "@ 2>&1) if ( $errMsg ) { Write-Error "$errMsg" $status = $status -replace 'Need ', 'Failed ' } else { $status = $status -replace 'Need ', '' } } catch { Write-Error "Writing $p failed!" } } else { $status = "Need $status" Write-Error "Cannot write into [$_]'$p'! Please re-run with sudo." } } elseif ( ($Force -or $RemoveOnly) -and $exists ) { $status = 'Need Removal' if ( $sudo -or $userFile ) { Write-Verbose "Removing [$_]'$p'..." try { Invoke-ExpressionEx -sudo:$shouldSudo Remove-Item $p if ( -not (Test-Path $p) ) { $status = 'Removed' } } catch { Write-Error "Removal of $p failed!" } } else { Write-Error "Cannot remove [$_]'$p'! Please re-run with sudo." } } if ( $status -ne 'Skipped' -or $ShowSkipped ) { if ( $shouldSudo -and $status -ne 'Skipped' ) { $status = "$status via sudo" } New-ProfileSetupStatus -Name $_ -Path $p -Status $status -Exist $(Test-Path $p) } } Remove-Item function:ProfileCode* -Confirm:$false } ####################################################################### ## ProfileCode_common (source) containing mandatory code for $PROFILE ####################################################################### function ProfileCode_common { function Get-PowerShellPath { Get-Process -PID $PID | ForEach-Object { $_.Path, $_.Parent.Path } | Where-Object { $_ -match 'powershell|pwsh' } | Select-Object -First 1 } function ConvertTo-Base64 { param([string]$String) return [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($String)) } function Invoke-ExpressionEx { [CmdletBinding(SupportsShouldProcess)]param( [switch]$sudo, [switch]$Force, [string]$Function, [switch]$MarshalOutput, [Parameter(Position = 1, ValueFromRemainingArguments = $true)] [string[]]$expr ) function do_sudo { [CmdletBinding()]param( [string]$sudo_cmd ) function do_sudo_win { [CmdletBinding()]param( [string]$sudo_cmd ) $local:sudo_exec = [string]::Empty if ( (Get-Command scoop -ErrorAction SilentlyContinue) ) { $sudo_exec = $(scoop which gsudo *>&1) if ( $sudo_exec -match 'not found' ) { $sudo_exec = $(scoop which sudo *>&1) } if ( $sudo_exec -match 'not found' ) { $sudo_exec = [string]::Empty } } if ( $sudo_exec ) { & $sudo_exec $sudo_cmd } else { Write-Error "Didn't find a known sudo command" } } if ( $PSVersionTable.Platform -eq 'Unix' ) { /usr/bin/env sudo $(Get-PowerShellPath) "-noprofile" "-EncodedCommand" $(ConvertTo-Base64 $sudo_cmd) } else { $local:currentIdenity = [System.Security.Principal.WindowsIdentity]::GetCurrent() if ( $currentIdenity.UserClaims.Value -contains 'S-1-5-32-544' ) { do_sudo_win $sudo_cmd } else { Write-Error "User [$($currentIdenity.Name)]does not have permissions to run as admin." } } } if ( $sudo -and -not $(Test-IsAdmin) ) { $local:tmpOutputFile = $null $local:tmpScriptFile = $null $local:sudo_cmd = "$($expr -join ' ')".Trim() if ( $Function ) { $tmpScriptFile = New-TemporaryFile -WhatIf:$false -Confirm:$false $tmpScriptFile = Move-Item $tmpScriptFile.FullName "$($tmpScriptFile.FullName).ps1" -PassThru -WhatIf:$false -Confirm:$false $local:tmpScriptContent = Export-FunctionSource $Function $sudo_cmd = "$Function $sudo_cmd" $tmpScriptContent | Out-File $tmpScriptFile.FullName -WhatIf:$false $expr = @( ".", $tmpScriptFile.FullName, ";", "`n$Function" ) + $expr } $expr = @( "`n`$WhatIfPreference=`$$WhatIfPreference;", "`n`$VerbosePreference='$VerbosePreference';", "`n`$ErrorActionPreference='$ErrorActionPreference';", "`n`$ConfirmPreference='$ConfirmPreference';`n" ) + $expr if ( $MarshalOutput ) { $tmpOutputFile = New-TemporaryFile -WhatIf:$false -Confirm:$false $expr += @( "| Export-CliXml -Path '$($tmpOutputFile.FullName)'" ) } $expr += @( ";`n exit `$LastExitCode" ) ## $tmpScriptContent += "$($expr -join ' ')".Trim() Write-Verbose "Performing the following command line via SUDO:`n{$sudo_cmd}" $sudo_cmd = "$($expr -join ' ')".Trim() do_sudo $sudo_cmd ## do_sudo 'Get-Content' $tmpScriptFile.FullName '|' 'Invoke-Expression' '|' 'Export-Clixml' '-Path' $tmpOutputFile.FullName #do_sudo $sudo_cmd if ( $tmpScriptFile ) { Remove-Item $tmpScriptFile } if ( $tmpOutputFile ) { Import-Clixml $tmpOutputFile; Remove-Item $tmpOutputFile } return } Write-Verbose "Perofrming the following expression in-line:`n{$($expr -join ' ')}" Invoke-Expression "$expr" } function Test-IsAdmin { [CmdletBinding(ConfirmImpact = 'None')] [OutputType([bool])] param() switch ($true) { $( ($PSVersionTable.PSEdition -eq 'Desktop') -or ($PSVersionTable.Platform -eq 'Win32NT') ) { # Fastest way on Windows ([Security.Principal.WindowsPrincipal]( [Security.Principal.WindowsIdentity]::GetCurrent() )).IsInRole( [Security.Principal.WindowsBuiltInRole]'Administrator' ) } $( ($PSVersionTable.PSEdition -eq 'Core') -and ($PSVersionTable.Platform -eq 'Unix') ) { # On macOS and Linux we use ID to figure out if we run elevated (0 means superuser rights) return $((id -u) -eq 0) } default { # Unable to figure it out! Write-Warning -Message 'Unknown' return } } } function Export-FunctionSource { param( [switch]$NoHeader, [Parameter(Position = 0, ValueFromRemainingArguments = $true)] #[ValidateSet([Functions])] [string[]]$FunctionName ) $local:src = "" foreach ( $local:func in $FunctionName ) { if ( -not $NoHeader ) { $src += "`nfunction $func {" } $src += "`n" $src += $((Get-Command -Type Function $func).Definition) if ( -not $NoHeader ) { $src += "`n}" } $src += "`n" } return $src.Trim() } # Are we running in Unix or Windows? $global:PathEnvDelimiter = $(if ( $PSVersionTable.Platform -match 'unix' ) { ':' } else { ';' }) function Split-PathEnv { param([string]$EnvPath) $EnvPath -split $PathEnvDelimiter } # Establish Module Path $global:MyPSModulePath = Split-PathEnv $env:PSModulePath | Where-Object { $_ -match "^$($(Resolve-Path ~) -replace '\\',"\\")" } | Where-Object { Test-Path $_ } | Get-Item | Sort-Object -Property LastWriteTime -Descending | Select-Object -ExpandProperty FullName -First 1 if ( -not $MyPSModulePath ) { $MyPSModulePath = $(Join-Path $(Join-Path $(Resolve-Path ~) 'powershell') 'Modules') $env:PSModulePath = "$MyPSModulePath$PathEnvDelimiter$env:PSModulePath" } if ( -not (Test-Path $MyPSModulePath) ) { New-Item -ItemType Directory -Path $MyPSModulePath -Force | Out-Null } Write-Verbose $(Get-Item $MyPSModulePath | Select-Object -ExpandProperty FullName) # Establish local/personal Script Root and make sure it's in $env:Path $local:p = Split-PathEnv $env:PATH $local:My_PSScriptRoot = Join-Path (Split-Path -Parent $MyPSModulePath) Scripts Write-Verbose "MyPSScriptRoot = $My_PSScriptRoot" if ( -not (Test-Path $My_PSScriptRoot) ) { New-Item -ItemType Directory -Path $My_PSScriptRoot -Force | Out-Null New-Item -ItemType Directory -Path $My_PSScriptRoot/profile.d -Force | Out-Null } $global:MyPSScriptRoot = $My_PSScriptRoot $p = @($p[0], $MyPSScriptRoot) + $($p | Select-Object -Skip 1) $env:PATH = $p -join $PathEnvDelimiter } $SetupFromWeb = [bool]$(@( $SetupFromWeb, $sfw, $LoadCode ) | Where-Object { $_ }) if ( -not $SetupFromWeb ) { _setup @PSBoundParameters Get-Item function:_setup | Remove-Item Get-Command Reload-MyScripts -ErrorAction SilentlyContinue | ForEach-Object { . $_.Name } } Remove-Variable SetupFromWeb -ErrorAction SilentlyContinue