Setup-Profile fixes + Edit-TextFile fixes

Setup-Profile: GitClone verbosity
Fixed: Existing $profile code will not be overwritten,
   instead profile code will be inserted before existing code.
Fixed: status message is reflected correctly:
  * Setup (replace/append)
  * Init (file created)
  * Clenaup (removal of code / deletion)
When profile code is removed, a placeholder remains behind,
  this will allow correct replacement if another setup is performed

Edit-TextFile:
Switched to Invoke-ExpressionEx for sudo invocation.
This commit is contained in:
Gal Szkolnik 2020-09-30 16:43:07 -04:00
parent 7117e78a7a
commit dc9f29f471
2 changed files with 228 additions and 218 deletions

View File

@ -7,7 +7,7 @@
$local:editors = $env:EDITOR,'nvim','code' $local:editors = $env:EDITOR,'nvim','code'
$local:editor = $null $local:editor = $null
foreach( $local:testEditor in $editors ) { foreach( $local:testEditor in $editors ) {
if( Get-Command -Type Application $testEditor -ErrorAction SilentlyContinue ) { if( $(try{Get-Command -Type Application $testEditor -ErrorAction SilentlyContinue}catch{}) ) {
$editor = $testEditor $editor = $testEditor
break; break;
} }
@ -17,13 +17,9 @@ if( $editor -match 'vim?$' ) {
$editor += ' -p' $editor += ' -p'
} }
if( $sudo ) {
$editor = "/usr/bin/env sudo $editor"
}
if( $editor -match 'code(\.exe)?$' ) { if( $editor -match 'code(\.exe)?$' ) {
if( -not (Get-Process -Name 'code' -ErrorAction SilentlyContinue) ) { if( -not (Get-Process -Name 'code' -ErrorAction SilentlyContinue) ) {
& $editor Invoke-ExpressionEx -sudo:$sudo $editor
} }
} }
@ -31,5 +27,5 @@ $local:arguments = $Path -join "' '"
if( $Path ) { $arguments = "'$arguments'" } if( $Path ) { $arguments = "'$arguments'" }
if( $PSCmdlet.ShouldProcess( "Edit ($editor): $arguments" ) ) { if( $PSCmdlet.ShouldProcess( "Edit ($editor): $arguments" ) ) {
Invoke-Expression "$editor $arguments" Invoke-ExpressionEx -sudo:$sudo $editor "$arguments"
} }

View File

@ -25,102 +25,115 @@ function _Setup {
## ProfileCode function (source) will contain the code to be written ## ProfileCode function (source) will contain the code to be written
####################################################################### #######################################################################
function ProfileCode_pre_common { function ProfileCode_pre_common {
# # If TMUX isn't loaded, start byobu, then exit this script (and current shell) # # If TMUX isn't loaded, start byobu, then exit this script (and current shell)
# if ( -not ( Test-Path env:TMUX ) ) { byobu; exit } # if ( -not ( Test-Path env:TMUX ) ) { byobu; exit }
} }
####################################################################### #######################################################################
## common Profile Code - receommended not to change this ## common Profile Code - receommended not to change this
## Code will be loaded from profile.d sub-directory after this. ## Code will be loaded from profile.d sub-directory after this.
####################################################################### #######################################################################
function ProfileCode_post_common { function ProfileCode_post_common {
# This loads the personal profile section from the $MyPSScriptRoot/profile.d directory # This loads the personal profile section from the $MyPSScriptRoot/profile.d directory
Get-Command Reload-MyScripts -ErrorAction SilentlyContinue | ForEach-Object { . $_.Name } Get-Command Reload-MyScripts -ErrorAction SilentlyContinue | ForEach-Object { . $_.Name }
} }
####################################################################### #######################################################################
## Setup-Profile code continues below ## _Setup Logic starts here
####################################################################### #######################################################################
if ( $GitClone -and -not (Test-Path $(Join-Path $MyPSScriptRoot '.git')) ) { if ( $GitClone -and -not (Test-Path $(Join-Path $MyPSScriptRoot '.git')) ) {
if ( -not [bool]$(Get-Command git -ErrorAction SilentlyContinue) ) { if ( -not [bool]$(Get-Command git -ErrorAction SilentlyContinue) ) {
throw "No git command found, you may either omit the -GitClone switch or install git and try again." throw "No git command found, you may either omit the -GitClone switch or install git and try again."
} }
if ( $PSCmdlet.ShouldProcess("Pull git repo from $GitURL into $MyPSScriptRoot ?") ) { if ( $PSCmdlet.ShouldProcess("Pull git repo from $GitURL into $MyPSScriptRoot ?") ) {
Push-Location $MyPSScriptRoot Push-Location $MyPSScriptRoot
$local:tmpGitDir = New-TemporaryFile $local:tmpGitDir = New-TemporaryFile
Remove-Item $tmpGitDir Remove-Item $tmpGitDir
New-Item -Type Directory $tmpGitDir.FullName | Out-Null New-Item -Type Directory $tmpGitDir.FullName | Out-Null
$local:GitOutput = "$(& git clone $GitURL $tmpGitDir.FullName --no-checkout)" Write-Verbose "Cloning git repo [$GitURL] into temporary location ('$($tmpGitDir.FullName)')..."
Write-Verbose $GitOutput $local:GitOutput = "$(& git clone $GitURL $tmpGitDir.FullName --no-checkout)"
Move-Item (Join-Path $tmpGitDir.FullName .git) ./.git Write-Verbose $GitOutput
Remove-Item $tmpGitDir.FullName Write-Verbose "Moving git repo from temp location to $MyPSScriptRoot . . ."
$GitOutput = "$(& git checkout --force)" Move-Item (Join-Path $tmpGitDir.FullName .git) ./.git
Write-Verbose $GitOutput Remove-Item $tmpGitDir.FullName
Pop-Location Write-Verbose "Checking out repo..."
} $GitOutput = "$(& git checkout --force)"
} Write-Verbose $GitOutput
Pop-Location
$local:ProfileSignature = [PSCustomObject]([ordered]@{ Write-Verbose "-GitClone step done."
Begin = '#### SZ Auto Profile Setup - BEGIN ####'
End = '#### SZ Auto Profile Setup - END ####'
Removed = '#### 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 -and ((Get-Content -Path $p | Out-String ) -match $ProfileSignature.Begin ) ) {
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.Begin ) {
$gate = $false;
$cleanupValue = $ProfileSignature.Removed
return $randomSeed;
}
return $_
}
} }
} }
$local:insertProfileCode = $false; $local:ProfileSignature = [PSCustomObject]([ordered]@{
if ( ($p -eq $_profile) -and -not $RemoveOnly ) { Begin = '#### SZ Auto Profile Setup - BEGIN ####'
$insertProfileCode = $true End = '#### SZ Auto Profile Setup - END ####'
Write-Verbose "Inserting ProfileCode into $($_)..." Removed = '#### SZ Auto Profile Setup - ----- ####'
$cleanupValue = $((@( 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, $ProfileSignature.Begin,
'', '',
@ -134,65 +147,66 @@ $_profiles | Foreach-Object {
'', '',
$ProfileSignature.End $ProfileSignature.End
) -join "`n" ).Trim().Replace(('$' + '_'), ('$$' + '_'))) ) -join "`n" ).Trim().Replace(('$' + '_'), ('$$' + '_')))
} }
$profileContent = $($profileContent -replace $randomSeed, $cleanupValue).Trim()
$local:shouldSudo = $sudo -and -not $userFile $profileContent = $($profileContent -replace $randomSeed, $cleanupValue).Trim()
$local:status = 'Skipped'
if ( $VerbosePreference -eq 'Continue' ) { $ShowSkipped = $true }
if ( -not ([string]::IsNullOrWhiteSpace($profileContent)) ) {
$status = "Need $(if($insertProfileCode){'Setup'}else{'Cleanup'})"
if ( $sudo -or $userFile ) {
Write-Verbose "Writing content to $($_)..."
$local:tmpOutput = New-TemporaryFile -WhatIf:$false
$profileContent | Out-File $tmpOutput.FullName -WhatIf:$false
if ( $shouldSudo -and $( $PSVersionTable.Platform -eq 'Unix' ) ) {
Invoke-ExpressionEx -sudo:$shouldSudo chmod 'a+r' $tmpOutput.FullName
}
try {
function BreakPointHere {} BreakPointHere;
$local:errMsg = $(Invoke-ExpressionEx -sudo:$shouldSudo "Move-Item $($tmpOutput.FullName) $p -Force:`$$Force -ErrorAction Stop | Out-Null" 2>&1) $local:shouldSudo = $sudo -and -not $userFile
if ( $errMsg ) { $local:status = 'Skipped'
Write-Error "$errMsg" if ( $VerbosePreference -eq 'Continue' ) { $ShowSkipped = $true }
$status = $status -replace 'Need ', 'Failed ' # 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
} }
else { try {
$status = $status -replace 'Need ', '' $local:errMsg = $(Invoke-ExpressionEx -sudo:$shouldSudo "Move-Item $($tmpOutput.FullName) $p -Force:`$$Force -ErrorAction Stop | Out-Null" 2>&1)
if ( $errMsg ) {
Write-Error "$errMsg"
$status = $status -replace 'Need ', 'Failed '
}
else {
$status = $status -replace 'Need ', ''
}
}
catch {
Write-Error "Writing $p failed!"
} }
} }
catch { else {
Write-Error "Writing $p failed!" $status = "Need $status"
Write-Error "Cannot write into [$_]'$p'! Please re-run with sudo."
} }
} }
else { elseif ( ($Force -or $RemoveOnly) -and $exists ) {
$status = "Need $status" $status = 'Need Removal'
Write-Error "Cannot write into [$_]'$p'! Please re-run with sudo." if ( $sudo -or $userFile ) {
} Write-Verbose "Removing [$_]'$p'..."
} try {
elseif ( ($Force -or $RemoveOnly) -and $exists ) { Invoke-ExpressionEx -sudo:$shouldSudo Remove-Item $p
$status = 'Need Removal' if ( -not (Test-Path $p) ) { $status = 'Removed' }
if ( $sudo -or $userFile ) { }
Write-Verbose "Removing [$_]'$p'..." catch {
try { Write-Error "Removal of $p failed!"
Invoke-ExpressionEx -sudo:$shouldSudo Remove-Item $p }
if ( -not (Test-Path $p) ) { $status = 'Removed' }
} }
catch { else {
Write-Error "Removal of $p failed!" Write-Error "Cannot remove [$_]'$p'! Please re-run with sudo."
} }
} }
else { if ( $status -ne 'Skipped' -or $ShowSkipped ) {
Write-Error "Cannot remove [$_]'$p'! Please re-run with sudo." if ( $shouldSudo -and $status -ne 'Skipped' ) { $status = "$status via sudo" }
New-ProfileSetupStatus -Name $_ -Path $p -Status $status -Exist $(Test-Path $p)
} }
} }
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 Remove-Item function:ProfileCode* -Confirm:$false
} }
####################################################################### #######################################################################
@ -218,93 +232,93 @@ function Invoke-ExpressionEx {
[string[]]$expr [string[]]$expr
) )
function do_sudo { function do_sudo {
[CmdletBinding()]param(
[string]$sudo_cmd
)
function do_sudo_win {
[CmdletBinding()]param( [CmdletBinding()]param(
[string]$sudo_cmd [string]$sudo_cmd
) )
$local:sudo_exec = [string]::Empty function do_sudo_win {
if ( (Get-Command scoop -ErrorAction SilentlyContinue) ) { [CmdletBinding()]param(
$sudo_exec = $(scoop which gsudo *>&1) [string]$sudo_cmd
if ( $sudo_exec -match 'not found' ) { )
$sudo_exec = $(scoop which sudo *>&1)
$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 ( $sudo_exec -match 'not found' ) { $sudo_exec = [string]::Empty }
} }
if ( $sudo_exec ) {
& $sudo_exec $sudo_cmd if ( $PSVersionTable.Platform -eq 'Unix' ) {
/usr/bin/env sudo $(Get-PowerShellPath) "-noprofile" "-EncodedCommand" $(ConvertTo-Base64 $sudo_cmd)
} }
else { else {
Write-Error "Didn't find a known sudo command" $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 ( $PSVersionTable.Platform -eq 'Unix' ) { if ( $sudo -and -not $(Test-IsAdmin) ) {
/usr/bin/env sudo $(Get-PowerShellPath) "-noprofile" "-EncodedCommand" $(ConvertTo-Base64 $sudo_cmd) $local:tmpOutputFile = $null
} $local:tmpScriptFile = $null
else {
$local:currentIdenity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $local:sudo_cmd = "$($expr -join ' ')".Trim()
if ( $currentIdenity.UserClaims.Value -contains 'S-1-5-32-544' ) { if ( $Function ) {
do_sudo_win $sudo_cmd $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
} }
else {
Write-Error "User [$($currentIdenity.Name)]does not have permissions to run as admin." $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"
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 { function Test-IsAdmin {
@ -346,15 +360,15 @@ param(
[string[]]$FunctionName [string[]]$FunctionName
) )
$local:src = "" $local:src = ""
foreach ( $local:func in $FunctionName ) { foreach ( $local:func in $FunctionName ) {
if ( -not $NoHeader ) { $src += "`nfunction $func {" } if ( -not $NoHeader ) { $src += "`nfunction $func {" }
$src += "`n" $src += "`n"
$src += $((Get-Command -Type Function $func).Definition) ### .Replace(('$'+'_'),('$$'+'_')) $src += $((Get-Command -Type Function $func).Definition)
if ( -not $NoHeader ) { $src += "`n}" } if ( -not $NoHeader ) { $src += "`n}" }
$src += "`n" $src += "`n"
} }
return $src.Trim() return $src.Trim()
} }
# Are we running in Unix or Windows? # Are we running in Unix or Windows?