Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Improve PowerShell elevation syntax with a wrapper function script #39

Closed
gerardog opened this issue May 30, 2020 · 19 comments
Closed

Comments

@gerardog
Copy link
Owner

gerardog commented May 30, 2020

For context: when writing this, gsudo is invoked as any other console .EXE app from PowerShell. This means the parsing/quote escaping is not ideal and this rules must be followed.

Looking forward to implement an `invoke-gsudo' function for PowerShell and I would like to hear opinions from people with more PowerShell experience than me.

  • This function would be a wrapper of gsudo.exe that would make it feel more PowerShell native.

  • The function name: What would be the best name for it? I bet people would throw me stones if It doesnt respect the verb-noun form. Ideally it should NOT be the same as in Support sudo <PowerShell cmdlet> 2 PowerShell/PowerShell#11343 which is hard to know since that one isn't defined yet either. (reason: to avoid all flows to break when that one is released). From now on I would just say invoke-gsudo as an alias for to be defined function name. Also, maybe it would be better to leave any alias definitions to the end user.

  • The deployment model: I think I figured out this one: By creating a Invoke-gsudo.ps1 file in the PATH (e.g. gsudo folder) would be enough. The function should be deployed by the 3 installers (scoop/choco/manual .ps1)

  • Input command parsing: Ideally one would just prepend invoke-gsudo without special quoting rules, but is that doable? Best way to get variable substitution? Would the PS-Remoting model work for gsudo?
    For example, this difference is unwanted: (related Incorrect quoted string parsing #38)

     PS> echo "abc def"
     abc def
     PS> gsudo echo "abc def"
     abc
     def
    
  • Output result marshalling: Since marshaling is impossible to avoid, this could be like: The elevated instance serializes the result instead of .ToString() it, stream (StdIn/Out) and non-elevated deserialize.

Reason I wrote this is here is because I prefer to gather feedback very early on. I don't want to invest time just to learn (after releasing) that I reinvented a wheel already available for free, in any of these areas.

@oising
Copy link

oising commented Jun 13, 2020

There isn't really a good reason to write a native (i.e. C# compiled) wrapper cmdlet as far as I can see? If you want to marshal parameters using powershell's binder to gsudo's, a wrapper function would do the trick. Or perhaps you're not aware that cmdlet infers compiled code, and function infers script?

@gerardog
Copy link
Owner Author

Oh, I didn't mean a native c# cmdlet, but a function script.
My intent is just to add one Invoke-gsudo.ps1 file side by side with gsudo.exe, both in the PATH.
Then you could Invoke-gsudo -ScriptBlock { Do-ElevatedStuff } or whatever syntax is more welcomed for PS developers than the current (quote-escaping-hell) syntax...

@oising
Copy link

oising commented Jun 14, 2020

I'd be happy to contribute, but that said, I haven't played with your magical powershell elevation. I suspect it secretly sends the work to an out of process instance of powershell. Am I right? Trying to pretend that a scriptblock is in the same lexical scope as the caller might lead to all kinds of weird situations as the variables wouldn't exist in that other runspace's sessionstate. Or are you doing something sneakier?

@gerardog
Copy link
Owner Author

That's right. gsudo.exe does very little to support elevating PS commands. The trick is that it is the user's responsibility to generate a string with a valid PS command (hence the quoting-hell) and then, when gsudo detects it's being called from PS, it elevates the same PowerShell.exe with -c {command}.

This can be seen with the --debug parameter:

PS $ gsudo "(Get-FileHash \""$file\"" -Algorithm $algorithm).Hash"

... executes gsudo "(Get-FileHash \"C:\test\test.txt\" -Algorithm MD5).Hash" which then calls pwsh again:

Debug: Command to run: "C:\Users\ggrignoli\scoop\apps\pwsh\current\pwsh.exe" -NoLogo -NoProfile -Command "(Get-FileHash \"C:\test\test.txt\" -Algorithm MD5).Hash"
(...)
79F1F6A36DBEBF6888E9E1717766F5A6

In that model, the user should be aware that there is no object marshalling, only strings in and out. In the example you can capture the result because .Hash returns a simple string, but good luck if it were a complex object.

So, what's next? The point of this issue is that I am open to suggestions and gather info on how far we can go.

Trying to pretend that a scriptblock is in the same lexical scope

It should be clear from the ground up that its not. There is a process-hop between elevated an unelevated instances so there will be serializing and deserializing of objects.

What is PS-Remoting doing with both local and remote scopes? Can we mimic that?

Take a look at this, it serializes a scriptblock while allowing the $using:LocalVariable syntax: it substitutes from $using:variableA to: $(Deserialize('serialized value of variableA')).

i.e. Write-Host $a becomes
Write-Host $([System.Management.Automation.PSSerializer]::Deserialize('&lt;Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell /2004/04"&gt;_x000D__x000A_ &lt;S&gt;HELLO&lt;/S&gt;_x000D__x000A_&lt;/Objs&gt;'))

AFAIK this would allow this syntax: Elevate-Command -ScriptBlock { $using:localVar + $remoteVar }, unless I am missing something.

Serialization will produce huge strings, but we can use pwsh -c - and then send the scriptblock via StdIn.
The output should be serialized before going to StdOut, then deserialized as well.

@gerardog
Copy link
Owner Author

e.g. Elevate-Command.ps1

$a = 10
Elevate-Command -ScriptBlock { $b = $using:a ; $b = $b+1 ; $b }

returns 11!

@gerardog
Copy link
Owner Author

gerardog commented Jun 17, 2020

New version (same link), supports -ArgumentList and pipe input is received via $Input.

$local = "outside scope"

"TestString" | Elevate-Command -ScriptBlock {
    $local = "inner scope"
    Write-Host "Am I elevated? $([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match 'S-1-5-32-544'))"
    Write-Host "Inner scope value: $local"
    Write-Host "Outer scope value: $using:local"
    Write-Host "Received Args: $args"
    Write-Host "Pipeline Input: $Input"
} -ArgumentList 1, 2, 3

returns

Am I elevated? True
Inner scope value: inner scope
Outer scope value: outside scope
Received Args: 1 2 3
Pipeline Input: TestString

I've tried to make Elevate-Command equivalent to Invoke-Command, except that the latter does not supports $using: syntax on localhost.
Does it sound practical? What could be improved? Opinions?

Now I've been looking at Ìnvoke-Expression... I am the exploring the idea of providing an Elevate-Expression function. I guess it will look similar to gsudo right now, but with a few differences:

  • Quoting rules would be exactly PS rules, not subtle differences like " => \""
  • Support $using: which will work by serializing the object using PSSerializer.

Any feedback at this early stage is highly welcome.

@oising
Copy link

oising commented Jun 17, 2020

So one thing you could do is to create a proxy command for Invoke-Expression to add an -Elevate parameter. Inventing new verbs (elevate) is frowned upon in the powershell world. You can have powershell automagically generate a wrapper function and pipe it to the clipboard like so:

[System.Management.Automation.ProxyCommand]::Create((gcm invoke-expression)) | clip

You end up with a function body (which you would wrap in a function Invoke-Expression { ... } block.) Functions have precedence over native Cmdlets.

[CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=2097030')]
param(
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [string]
    ${Command})

begin
{
    try {
        $outBuffer = $null
        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
        {
            $PSBoundParameters['OutBuffer'] = 1
        }

        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Invoke-Expression', [System.Management.Automation.CommandTypes]::Cmdlet)
        $scriptCmd = {& $wrappedCmd @PSBoundParameters }

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
    } catch {
        throw
    }
}

process
{
    try {
        $steppablePipeline.Process($_)
    } catch {
        throw
    }
}

end
{
    try {
        $steppablePipeline.End()
    } catch {
        throw
    }
}
<#

.ForwardHelpTargetName Microsoft.PowerShell.Utility\Invoke-Expression
.ForwardHelpCategory Cmdlet

#>

You would then add a new switch parameter to the param block, [switch]$Elevate. This is a reasonable way to add new functionality to an out of the box cmdlet. But given that you're pushing the work to gsudo, you'd rip out the steppable pipeline stuff and replace with your own marshalling script. Just an idea.

@oising
Copy link

oising commented Jun 17, 2020

btw, powershell.exe and pwsh.exe can automatically deserialize psserialized objects from a native command (e.g. cmd batch, exe or other out of proc process) if the stdin stream is prefixed with a special marker sequence. Check this out:

Create a batch file foo.cmd (escaping < and > with ^)

@echo #^< CLIXML
@echo ^<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"^>^<I32^>42^</I32^>^</Objs^>

Now, pipe this command to foreach %

.\foo.cmd | % { $_ }
42

Magic!

So if your out of proc elevation serializes the output, then we could pipe an inline elevated expression to another cmdlet/function without having to insert ugly deserialization code.

@oising
Copy link

oising commented Jun 17, 2020

Oh this could also help - you can get powershell to serialize AND add the magic marker itself like this:

pwsh -noprofile -outputformat xml -command { gi c:\windows }
#< CLIXML
<Objs ... >

Example rehydrating in another pwsh process:

pwsh -noprofile -outputformat xml -command { gi c:\windows } | pwsh -noprofile -command { $input | % { $_ } }
Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2020-06-15  2:58 PM                Windows

@gerardog gerardog changed the title Feature: Native PowerShell CmdLet for gsudo [Help Wanted] Feature: Improve PowerShell elevation syntax with a wrapper function script [Help Wanted] Jul 31, 2020
@majkinetor
Copy link

I bet people would throw me stones if It doesnt respect the verb-noun form.

The correct way is to name it in verb-noun (Invoke-Gsudo) manner and set alias to whatever (gsudo).

@nickcox
Copy link

nickcox commented Jan 31, 2021

For the issue around quoting rules, you can access the invoked command with $MyInvocation. This seems to work for me so far:

function Invoke-Gsudo {
  gsudo.exe ($MyInvocation.Line -replace "^$($MyInvocation.InvocationName)\s+" -replace '"','""')
}

❯ set-alias sudo Invoke-Gsudo
❯ sudo echo "abc def"
abc def

❯ _

@mattcargile
Copy link

mattcargile commented Jan 7, 2022

The -EncodedCommand parameter seems helpful here too. It would save much of the quoting headaches.

I use it in this function I stole from Lee Holmes in order to run psexec , sysinternals tool.

Here is the applicable piece of code which appears in the help for pwsh and powershell , i think.

## $expression -eq [scriptblock]
## Convert the command into an encoded command for PowerShell
$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
$encodedCommand = [Convert]::ToBase64String($commandBytes)
$commandLine += "-EncodedCommand $encodedCommand"

@mattcargile
Copy link

mattcargile commented Jan 24, 2022

I was playing around with the new build. I couldn't understand the piped $InputObject piece. When I removed it, code like the below worked.

Invoke-gsudo -ScriptBlock { Get-Content .\readline.ps1 } -NoElevate

try { ($InputObject | Invoke-Command $sb -ArgumentList $argumentList ) } catch { Write-Output $_ }

Some of this may feed into the $Input variable not being defined. Should $Input be $InputObject? I see now, I didn't know about the $Input automatic variable.

$InputArray = $Input

When I changed the above line to $InputObject , I received the below error.

ErrorRecord                 : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and
                              its properties do not match any of the parameters that take pipeline input.
WasThrownFromThrowStatement : False
TargetSite                  : System.Collections.ObjectModel.Collection`1[System.Management.Automation.PSObject] Invoke(System.Collections.IEnumerable)
Message                     : The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The input object
                              cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties
                              do not match any of the parameters that take pipeline input.
Data                        : {System.Management.Automation.Interpreter.InterpretedFrameInfo}
InnerException              :
HelpLink                    :
Source                      : System.Management.Automation
HResult                     : -2146233087
StackTrace                  :    at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
                                 at Microsoft.PowerShell.Executor.ExecuteCommandHelper(Pipeline tempPipeline, Exception& exceptionThrown, ExecutionOptions options)

@gerardog
Copy link
Owner Author

gerardog commented Jan 24, 2022

Matt! You anticipated me. I will post what I had in draft and then address your comments:

Hi again!
Sorry for not having the time to focus on gsudo as much as I wanted lately. Let's try to move this one forward a little bit.

There is an experimental Invoke-gsudo.ps1 that I want to open for early testing. Put it in your path, preferable in gsudo's folder (get-command gsudo.exe | % { (get-item $_.Source ).Directory.FullName })

This is a glimpse of current status:

.DESCRIPTION
Serializes a scriptblock and executes it in an elevated powershell. 
The ScriptBlock runs in a different process, so it can´t read/write variables from the invoking scope.
If you reference a variable in a scriptblock using the `$using:variableName` it will be replaced with it´s serialized value.
The elevated command can accept input from the pipeline with $Input. It will be serialized, so size matters.
The script result is serialized, sent back to the non-elevated instance, and returned.
Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (UAC popup cancelled) 

.EXAMPLE
PS> Get-Process notepad | Invoke-gsudo { Stop-Process }

PS> Invoke-gsudo { return Get-Content 'C:\My Secret Folder\My Secret.txt'}

PS> $a=1; $b = Invoke-gsudo { $using:a+10 }; Write-Host "Sum returned: $b";

Sum returned: 11

Problems/Challenges

  • Steppable input is supported, but serialized all at once. (It's not streamed one-by-one). Large input will probably cause issues.
    I don't think this is such a big issue at this point since the user could move the elevation boundary to minimize the serialization and performance hit.

  • Commands return issue: EDIT: FIXED
    As of now, Invoke-gsudo is requiring an extra 'return' statement on some cases. For example:

    • PS C:\> Invoke-Command { Get-Content C:\Test.txt } for reference, Invoke-Command works (returns file content).
    • PS C:\> Invoke-gsudo { Get-Content C:\Test.txt } doesn't work, returns nothing.
    • PS C:\> Invoke-gsudo { return Get-Content C:\Test.txt } works.
    • PS C:\> Invoke-gsudo { Get-Content C:\Test.txt ; 1 } works, returns the content and a 1.
  • Exception handling:

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

  • Only works in TokenMode, at least for now.
    Basically dont change gsudo settings like: SecurityEnforceUacIsolation or ForceXXXConsole

Awaiting feedback mode: ON

@gerardog
Copy link
Owner Author

Matt, $inputObject allows to do Get-Process notepad | Invoke-gsudo { Stop-Process }. You will see it as $Input inside the ScriptBlock since it is an automatic variable generated by Pwsh Invoke-Command.
Great catch it's causing the 'return' issue. Pushing a fix right now.

@mattcargile
Copy link

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

I would think we would let the users determine the ErrorAction flow within the [scriptblock] passed into Invoke-gsudo.ps1? I would assume the aforementioned line should avoid declaring an ErrorAction and let the users $ErrorActionPreference come into play.

As far as telling the difference between the errors, I was reading this and this. Maybe we can use one of these:

  • $Error.CategoryInfo.Category
  • $Error.Exception.WasThrownFromThrowStatement

Category appears to always be OperationStopped due to a System.Management.Automation.PipelineStoppedException being called by ThrowTerminatingError(ErrorRecord). This is opposed to a non-terminating error which calls System.Management.Automation.Cmdlet.WriteError. Not sure if we need to tell a difference between Write-Error 1 and Write-Error 1 -ErrorAction Stop?

I may be confused with the use cases for the error handling. I was playing with the below.

Invoke-gsudo.ps1 { write-error 1; write-error 2; 2 }

I would expect it to return

Invoke-gsudo.ps1: 1
Invoke-gsudo.ps1: 2
2

Currently it is returning

2
Invoke-gsudo.ps1: 1

I guess it is because of the redirection ( 2>&1 ) changing the order of the errors and then the ErrorAction on the aforementioned line.

As another example, I was playing with the below which does work as I would expect.

Invoke-gsudo.ps1 { throw 'hello world'; 2 }

@gerardog
Copy link
Owner Author

I'm not able to distinguish between Terminating (Invoke-gsudo { throw}) and non-terminating errors ( Invoke-gsudo { Write-Error 1; Write-Error 2;}) here
and I think the ErrorAction needs to be Stop for the formers so they trigger catch blocks.

TIL: Apparently, nobody can. See PowerShell/PowerShell#3158

I would think we would let the users determine the ErrorAction flow within the [scriptblock] passed into Invoke-gsudo.ps1? I would assume the aforementioned line should avoid declaring an ErrorAction and let the users $ErrorActionPreference come into play.

I agree. I removed specifying the fixed ErrorAction you are referring to.

  • $Error.Exception.WasThrownFromThrowStatement

Thanks, that really helped. Now I do try/catch on the scriptblock to detect the terminating errors, and force throw $_ to ensure WasThrownFromThrowStatement is true. Works 99% 🤷‍♂️.

Now, should I honor Invoke-gsudo -ErrorAction parameter? Invoke-Command does not. No difference between:
- Invoke-Command { "/", "/nofound", "/" | Get-Item } -ErrorAction Stop
- Invoke-Command { "/", "/nofound", "/" | Get-Item }

It felt harmless to take the -ErrorAction param (IF specified) and setting that as $ErrorActionPreference in the elevated instance. Opinions?

If omitted the invokers ErrorActionPreference is forwarded to the elevated instance.

I may be confused with the use cases for the error handling.

Me too. I'm learning as I code here. Found this great recap on error handling on PS: See MicrosoftDocs/PowerShell-Docs#1583
I'd mimic Invoke-Command assuming it's doing the proper thing, or what the user would expect, plus that is easier to communicate.

In a nutshell, keep the same exception type, keep the same stream. For example:

  • Non-terminating errors:
    • Should not terminate the pipeline execution.
    • Capturable with -ErrVarible or 2>&1
    • E.g. Invoke-gsudo { "\", "notExisting" | Get-Item } } -errVariable failed
  • Terminating errors
    • Capturable with try/catch
    • try { Invoke-Gsudo { throw; } } catch {"Catched: $_"}

Now, I still have problems between Script-terminating errors and Statement-terminating errors.
Apparently surrounding the script in a try/catch turns a Statement-terminating error into a Script-terminating error (in this context, actually into a scriptblock-terminating).

For example, Invoke-Command behaves differently surrounded by try/catch:

PS C:\Users\gerar> Invoke-Command { Some-InvalidCmdLet ; 10 }
Some-InvalidCmdLet: The term 'Some-InvalidCmdLet' is not recognized as a name of a cmdlet, function, script file, or executable program.
10

PS C:\Users\gerar> try { Invoke-Command { Some-InvalidCmdLet ; 10 } } catch { "Catched: $_" }
Catched: The term 'Some-InvalidCmdLet' is not recognized as a name of a cmdlet, function, script file, or executable program.

The later stops execution at Some-InvalidCmdLet and never returns 10.
The problem here is that I use try catch to detect termining errors.

So, is it correct to conclude that all statement-terminating errors will turn into script-terminating?

I was playing with the below.

Invoke-gsudo.ps1 { write-error 1; write-error 2; 2 }

I would expect it to return

Invoke-gsudo.ps1: 1
Invoke-gsudo.ps1: 2
2

Currently it is returning

2
Invoke-gsudo.ps1: 1

I guess it is because of the redirection ( 2>&1 ) changing the order of the errors and then the ErrorAction on the aforementioned line.

Yes, the redirection changes the output order. No workaround AFAIK.
Also, the second exception was lost. Should be fixed now.

Here are a few test cases. Ideally each pair of lines should behave the same, but they don't. (Invoke-Command vs Invoke-gsudo).

Proper PS tests may exists in the future, for now a comment will do.

try { invoke-gsudo { 0/0 } } catch { "catched: $_" }
try { Invoke-Command { 0/0 } } catch { "catched: $_" }

Invoke-gsudo { 0/0;0/0 } # fail: only 1 exception, caused by try/catch
Invoke-Command { 0/0;0/0 } 

try { invoke-gsudo   { "asd" | ConvertFrom-Json } } catch { "catched: $_" } 
try { Invoke-Command { "asd" | ConvertFrom-Json } } catch { "catched: $_" } # Weird, why catched? Oh.. PowerShell/PowerShell#2860

try { Invoke-Command { throw } } catch { "catched: $_" }
try { invoke-gsudo   { throw } } catch { "catched: $_" }

try { invoke-Command { "notfound" | Get-Item } } catch { "catched: $_" }
try { invoke-gsudo   { "notfound" | Get-Item } } catch { "catched: $_" }

try { invoke-Command { "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }
try { invoke-gsudo   { "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }

try { invoke-Command { ".", "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }
try { invoke-gsudo   { ".", "notfound" | Get-Item -ErrorAction Stop } } catch { "catched: $_" }

#weird fail. Also, run as admin
try { invoke-command { Get-Service "netlogon" | Suspend-Service -ErrorAction Stop }  } catch { "catched: $_" }
try { invoke-gsudo { Get-Service "netlogon" | Suspend-Service -ErrorAction Stop }  } catch { "catched: $_" }

try { invoke-Command { Get-InvalidCmdLet ; 1 } } catch { "catched: $_" }
try { invoke-gsudo   { Get-InvalidCmdLet ; 1 } } catch { "catched: $_" }

try { invoke-gsudo { [int]::Parse('foo') }  } catch { "e $_"  }
try { invoke-command { [int]::Parse('foo') }  } catch { "e $_"  }

I've just pushed a new version. Feedback welcomed!

@gerardog
Copy link
Owner Author

gerardog commented Feb 3, 2022

Uploaded tests, and bugfixing.

PS C:\git\gsudo> Invoke-Pester -Output Detailed
Pester v5.3.1

Starting discovery in 2 files.
Discovery found 16 tests in 205ms.
Running tests.

Running tests from 'C:\git\gsudo\src\gsudo.extras\gsudo.Tests.ps1'
Describing PS Gsudo (v7.2.1)
  [+] It serializes return values as string. 2.33s (2.3s|38ms)
  [+] When invoked as gsudo !!, It elevates the last command executed 452ms (451ms|1ms)

Running tests from 'C:\git\gsudo\src\gsudo.extras\Invoke-gsudo.Tests.ps1'
Describing PS Invoke-Gsudo (v7.2.1)
  [+] It serializes return values maintaining its type 569ms (568ms|1ms)
  [+] It serializes return values mantaining its properties. 503ms (502ms|1ms)
  [+] It returns an array of values mantaining its properties. 524ms (523ms|1ms)
  [+] It accepts objects from the pipeline. 482ms (480ms|2ms)
  [+] It throws when Error thrown 622ms (621ms|1ms)
  [+] It throws with expression runtime errors 1.14s (1.14s|1ms)
  [+] It throws with .Net Exceptions 577ms (577ms|1ms)
  [+] It throws when ErrorAction = Stop 570ms (570ms|1ms)
  [+] It throws when ErrorActionPreference = Stop 550ms (550ms|0ms)
  [+] It forwards ErrorActionPreference 'Stop' to the elevated instance 527ms (525ms|2ms)
  [+] It forwards ErrorActionPreference 'Continue' to the elevated instance 498ms (498ms|1ms)
  [+] It forwards ErrorActionPreference 'Ignore' to the elevated instance 494ms (493ms|1ms)
  [+] It doesn't throw when ErrorActionPreference = Continue 1.23s (1.23s|1ms)
  [+] It doesn't throw with '-ErrorAction Continue-' 2.69s (2.69s|1ms)
Tests completed in 49.29s
Tests Passed: 16, Failed: 0, Skipped: 0 NotRun: 0

Also on the build server: CI test results with inconvenient ordering.

And last, but not least: I added a gsudo PowerShell Module: (Add Import-Module pathto/gsudoModule.psm1 to your $PROFILE) which adds support for gsudo !! too. (As requested in #44)

@gerardog
Copy link
Owner Author

Released in v1.1.0

@gerardog gerardog changed the title Feature: Improve PowerShell elevation syntax with a wrapper function script [Help Wanted] Feature: Improve PowerShell elevation syntax with a wrapper function script Mar 11, 2022
@gerardog gerardog removed the help wanted Extra attention is needed label Mar 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants