Skip to content

Commit

Permalink
Preserve console title after gsudo exit, but bulletproof JIC (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardog committed Aug 27, 2023
1 parent 2e698e0 commit db7bf90
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 1 deletion.
182 changes: 182 additions & 0 deletions src/gsudo.Wrappers/Invoke-ElevatedCommand.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<#
.SYNOPSIS
Executes a ScriptBlock or script file in a new elevated instance of PowerShell using gsudo.
.DESCRIPTION
The Invoke-ElevatedCommand cmdlet serializes a ScriptBlock or script file and executes it in an elevated PowerShell instance using gsudo. The elevated command runs in a separate process, which means it cannot directly access or modify variables from the invoking scope.
The cmdlet supports passing arguments to the ScriptBlock or script file using the -ArgumentList parameter, which can be accessed with the $args automatic variable within the ScriptBlock or script file. Additionally, you can provide input from the pipeline using $Input, which will be serialized and made available within the elevated execution context.
The result of the elevated command is serialized, sent back to the non-elevated instance, deserialized, and returned. This means object graphs are recreated as PSObjects.
Optionally, you can check for "$LastExitCode -eq 999" to determine if gsudo failed to elevate, such as when the UAC popup is cancelled.
.PARAMETER ScriptBlock
Specifies a ScriptBlock to be executed in an elevated PowerShell instance. For example: { Get-Process Notepad }
.PARAMETER FilePath
Specifies the path to a script file to be executed in an elevated PowerShell instance.
.PARAMETER ArgumentList
Provides a list of elements that will be accessible inside the ScriptBlock or script as $args[0], $args[1], and so on.
.PARAMETER LoadProfile
Loads the user profile in the elevated PowerShell instance, regardless of the gsudo configuration setting PowerShellLoadProfile.
.PARAMETER NoProfile
Does not load the user profile in the elevated PowerShell instance.
.PARAMETER RunAsTrustedInstaller
Runs the command as the TrustedInstaller user.
.PARAMETER RunAsSystem
Runs the command as the SYSTEM user.
.PARAMETER ClearCache
Clears the gsudo cache before executing the command.
.PARAMETER NewWindow
Opens a new window for the elevated command.
.PARAMETER KeepNewWindowOpen
Keeps the new window open after the elevated command finishes execution.
.PARAMETER KeepShell
Keeps the shell open after the elevated command finishes execution.
.PARAMETER NoKeep
Closes the shell after the elevated command finishes execution.
.PARAMETER InputObject
You can pipe any object to this function. The object will be serialized and available as $Input within the ScriptBlock or script.
.INPUTS
System.Object
.OUTPUTS
The output of the ScriptBlock or script executed in the elevated context.
.EXAMPLE
Invoke-ElevatedCommand { return Get-Content 'C:\My Secret Folder\My Secret.txt' }
.EXAMPLE
Get-Process notepad | Invoke-ElevatedCommand { $input | Stop-Process }
.EXAMPLE
$a = 1; $b = Invoke-ElevatedCommand { $args[0] + 10 } -ArgumentList $a; Write-Host "Sum returned: $b"
Sum returned: 11
.EXAMPLE
Invoke-ElevatedCommand { Get-Process explorer } | ForEach-Object { $_.Id }
.LINK
https://github.com/gerardog/gsudo
#>
[CmdletBinding(DefaultParameterSetName='ScriptBlock')]
param
(
# The script block to execute in an elevated context.
[Parameter(Mandatory = $true, Position = 0, ParameterSetName='ScriptBlock')] [System.Management.Automation.ScriptBlock]
[ArgumentCompleter( { param ()
# Auto complete with last 5 ran commands.
$lastCommands = Get-History | Select-Object -last 5 | % { "{ $($_.CommandLine) }" }

if ($lastCommands -is [System.Array]) {
# Last one first.
$lastCommands[($lastCommands.Length - 1)..0] | % { $_ };
}
elseif ($lastCommands) {
# Only one command.
$lastCommands;
}
} )]

$ScriptBlock,

# Alternarive file to be executed in an elevated PowerShell instance.
[Parameter(Mandatory = $true, ParameterSetName='ScriptFile')] [String] $FilePath,

[Parameter(Mandatory = $false)] [System.Object[]] $ArgumentList,

[Parameter(ParameterSetName='ScriptBlock')] [switch] $LoadProfile,
[Parameter(ParameterSetName='ScriptBlock')] [switch] $NoProfile,

[Parameter()] [switch] $RunAsTrustedInstaller,
[Parameter()] [switch] $RunAsSystem,
[Parameter()] [switch] $ClearCache,

[Parameter()] [switch] $NewWindow,
[Parameter()] [switch] $KeepNewWindowOpen,
[Parameter()] [switch] $KeepShell,
[Parameter()] [switch] $NoKeep,

[ValidateSet('Low', 'Medium', 'MediumPlus', 'High', 'System')]
[System.String]$Integrity,

[Parameter()] [System.Management.Automation.PSCredential] $Credential,
[Parameter(ValueFromPipeline)] [pscustomobject] $InputObject
)
Begin {
$inputArray = @()
}
Process {
foreach ($item in $InputObject) {
# Add the modified item to the output array
$inputArray += $item
}
}
End {
$gsudoArgs = @()

if ($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $gsudoArgs += '--debug' }

if ($LoadProfile) { $gsudoArgs += '--LoadProfile' }
if ($RunAsTrustedInstaller) { $gsudoArgs += '--ti' }
if ($RunAsSystem) { $gsudoArgs += '-s' }
if ($ClearCache) { $gsudoArgs += '-k' }
if ($NewWindow) { $gsudoArgs += '-n' }
if ($KeepNewWindowOpen) { $gsudoArgs += '--KeepWindow' }
if ($NoKeep) { $gsudoArgs += '--close' }
if ($Integrity) { $gsudoArgs += '--integrity'; $gsudoArgs += $Integrity}

if ($Credential) {
$CurrentSid = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value;
$gsudoArgs += "-u", $credential.UserName

# At the time of writing this, there is no way (considered secure) to send the password to gsudo. So instead of sending the password, lets start a credentials cache instance.
$p = Start-Process "gsudo.exe" -Args "-u $($credential.UserName) gsudoservice $PID $CurrentSid All 00:05:00" -credential $Credential -LoadUserProfile -WorkingDirectory "$env:windir" -WindowStyle Hidden -PassThru
$p.WaitForExit();
Start-Sleep -Seconds 1
}

if ($PSVersionTable.PSVersion.Major -le 5) {
$pwsh = "powershell.exe"
} else {
$pwsh = "pwsh.exe"
}

if ($ScriptBlock) {
if ($NoProfile) {
$gsudoArgs += '-d';
$gsudoArgs += $pwsh;
$gsudoArgs += '-NoProfile';
$gsudoArgs += '-NoLogo';

if ($KeepShell) { $gsudoArgs += '--NoExit' }
} else {
if ($KeepShell) { $gsudoArgs += '--KeepShell' }
}

if ($myInvocation.expectingInput) {
$inputArray | gsudo.exe @gsudoArgs $ScriptBlock -args $ArgumentList
} else {
gsudo.exe @gsudoArgs $ScriptBlock -args $ArgumentList
}
} else {
if ($myInvocation.expectingInput) {
$inputArray | gsudo.exe @gsudoArgs -args $ArgumentList
} else {
gsudo.exe @gsudoArgs -d $pwsh -File $FilePath -args $ArgumentList
}
}
}
7 changes: 6 additions & 1 deletion src/gsudo/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ public async Task<int> Execute()
}
finally
{
Console.Title = originalWindowTitle;
try
{
Console.Title = originalWindowTitle;
}
catch
{ }
}
}

Expand Down

0 comments on commit db7bf90

Please sign in to comment.