public/functions/Remove-PowerStubFromProfile.ps1
|
<# .SYNOPSIS Removes the lines that import PowerStub from a PowerShell profile. .DESCRIPTION Undoes Add-PowerStubToProfile, and also removes imports added by hand, such as 'Import-Module PowerStub' or an import of a PowerStub.psd1 path. The '# PowerStub' comment that Add-PowerStubToProfile writes above its line is removed with it. Only a statement that is the whole of its line(s) is removed. An import sharing a line with other code is left alone and reported with a warning. The profile must parse cleanly before and after the change, otherwise nothing is written. The file keeps its encoding, and a symbolic link to a dotfiles profile is written through. This does not unload PowerStub from the current session. .PARAMETER Path The profile file to change. Defaults to $PROFILE.CurrentUserAllHosts, the profile Add-PowerStubToProfile writes. Pass $PROFILE for the current host's profile. .EXAMPLE Remove-PowerStubFromProfile Removes the PowerStub import from the current user's all-hosts profile. .EXAMPLE Remove-PowerStubFromProfile -Path $PROFILE -WhatIf Shows what would be removed from the current host's profile without changing it. .OUTPUTS PSCustomObject with Path, Lines (the import statements removed) and Removed. #> function Remove-PowerStubFromProfile { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [string]$Path = $PROFILE.CurrentUserAllHosts ) if (-not $Path) { throw 'This session has no $PROFILE path. Pass the profile file with -Path.' } $Path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) $profileInfo = Read-PowerStubProfile -Path $Path if ($profileInfo.Errors) { $first = $profileInfo.Errors[0] throw "Profile '$Path' has syntax errors (line $($first.Extent.StartLineNumber): $($first.Message)). Fix them, then run Remove-PowerStubFromProfile again. Nothing was changed." } $text = $profileInfo.Text $ranges = [System.Collections.Generic.List[object]]::new() $lines = [System.Collections.Generic.List[string]]::new() foreach ($import in $profileInfo.Imports) { # Remove the whole statement, so only a pipeline made of just this command qualifies $statement = $import.Parent if ($statement -isnot [System.Management.Automation.Language.PipelineAst] -or $statement.PipelineElements.Count -ne 1) { Write-Warning "Profile '$Path' line $($import.Extent.StartLineNumber): '$($import.Extent.Text)' is part of a larger statement. Remove it by hand." continue } $statementStart = $statement.Extent.StartOffset $statementEnd = $statement.Extent.EndOffset $start = if ($statementStart -eq 0) { 0 } else { $text.LastIndexOf("`n", $statementStart - 1) + 1 } $end = $text.IndexOf("`n", $statementEnd) $end = if ($end -lt 0) { $text.Length } else { $end + 1 } if ($text.Substring($start, $statementStart - $start).Trim() -or $text.Substring($statementEnd, $end - $statementEnd).Trim()) { Write-Warning "Profile '$Path' line $($import.Extent.StartLineNumber): '$($import.Extent.Text)' shares its line with other code. Remove it by hand." continue } # Also take the '# PowerStub' marker above it, and the blank line Add-PowerStubToProfile put above that $previousLineStart = { param($lineStart) if ($lineStart -le 1) { 0 } else { $text.LastIndexOf("`n", $lineStart - 2) + 1 } } if ($start -gt 0) { $markerStart = & $previousLineStart $start if ($text.Substring($markerStart, $start - $markerStart).Trim() -eq '# PowerStub') { $start = $markerStart if ($start -gt 0) { $blankStart = & $previousLineStart $start if (-not $text.Substring($blankStart, $start - $blankStart).Trim()) { $start = $blankStart } } } } $ranges.Add([PSCustomObject]@{ Start = $start; End = $end }) $lines.Add($statement.Extent.Text) } if ($ranges.Count -eq 0) { Write-Verbose "Profile '$Path' has no PowerStub import to remove" return [PSCustomObject]@{ Path = $Path; Lines = @(); Removed = $false } } $newText = $text foreach ($range in ($ranges | Sort-Object Start -Descending)) { $newText = $newText.Remove($range.Start, $range.End - $range.Start) } $tokens = $null $errors = $null [System.Management.Automation.Language.Parser]::ParseInput($newText, [ref]$tokens, [ref]$errors) | Out-Null if ($errors) { throw "Removing PowerStub from profile '$Path' would leave syntax errors (line $($errors[0].Extent.StartLineNumber): $($errors[0].Message)). Remove it by hand. Nothing was changed." } if (-not $PSCmdlet.ShouldProcess($Path, "Remove $($lines -join ', ')")) { return } [System.IO.File]::WriteAllText($Path, $newText, $profileInfo.Encoding) Write-Verbose "Removed $($lines.Count) PowerStub import(s) from profile '$Path'. New sessions will not load PowerStub." [PSCustomObject]@{ Path = $Path; Lines = $lines.ToArray(); Removed = $true } } |