diff --git a/src/gsudo.Wrappers/Invoke-ElevatedCommand.ps1 b/src/gsudo.Wrappers/Invoke-ElevatedCommand.ps1 new file mode 100644 index 00000000..10635c0a --- /dev/null +++ b/src/gsudo.Wrappers/Invoke-ElevatedCommand.ps1 @@ -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 + } + } +} diff --git a/src/gsudo/Commands/RunCommand.cs b/src/gsudo/Commands/RunCommand.cs index 302165dd..0302ef66 100644 --- a/src/gsudo/Commands/RunCommand.cs +++ b/src/gsudo/Commands/RunCommand.cs @@ -85,7 +85,12 @@ public async Task Execute() } finally { - Console.Title = originalWindowTitle; + try + { + Console.Title = originalWindowTitle; + } + catch + { } } }