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

Issue: Setting PowerShell's location to any provider other than FileSystem is not supported. #124

Closed
mattcargile opened this issue Jan 16, 2022 · 11 comments

Comments

@mattcargile
Copy link

Issue Description

Both gsudo and sudo won't handle the Registry Provider path when using the dot notation ( . ) for $PWD. Execution places me in my previous FileSystem provider directory instead of the current Registry Provider Directory such as HKLM: So when using Get-ItemProperty I return a FileInfo object of previous FileSystem Provider and when using Set-ItemProperty pwsh.exe throws an error.

Steps to Reproduce

  1. pwsh.exe
  2. Set-Location $HOME
  3. Set-Location HKLM:\SOFTWARE
  4. sudo Get-ItemProperty -Path .

Workaround

I pass $PWD.Path instead of the period.

Additionally, after typing this up I am noticing it is most likely a powershell issue? I looked quickly and couldn't find an article or Issue surrounding it.

Screenshots

--debug Output

Debug: Command to run: "C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -NoProfile -Command "get-itemproperty -path ."
Debug: Using Console mode TokenSwitch
Debug: CreateProcessAsUser: "C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -NoProfile -Command "get-itemproperty -path ."
Debug: Elevating process: C:\ProgramData\chocolatey\lib\gsudo\bin\gsudo.exe --debug gsudoelevate --pid 23596
Debug: Elevated instance started.

    Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d--h-           1/15/2022  9:28 PM                ProgramData

Debug: Process exited with code 0

Context:

  • Windows version: Windows 10 Version 1909 ( OS Build 18363.1377 )
  • gsudo version: gsudo v1.0.2 (Branch.master.Sha.cab27ed3ca23496129320dfed56624274c9a27a6)
@mattcargile
Copy link
Author

mattcargile commented Jan 16, 2022

Now I found a link. I didn't realize there was an [Environment]::CurrentDirectory as well that could be used. I wonder if this could be preset with the Registry Provider. Scratch that, I see now gsudo is grabbing the path somehow...

I did test with pwsh.exe's -WorkingDirectory parameter and it does work to set it as the $PWD if it is a Registry Provider. Would it be reasonable to check if current running process was pwsh.exe and the use the aforementioned parameter?

I used sudo --debug pwsh -workingdirectory $pwd -c 'get-itemproperty .'

Debug: Command to run: "C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -NoProfile -Command "pwsh -workingdirectory \"HKLM:\SOFTWARE\WOW6432Node\Cisco\Cisco AnyConnect Secure Mobility Client\\\" -c \"get-itemproperty .\""
Debug: Using Console mode TokenSwitch
Debug: CreateProcessAsUser: "C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -NoProfile -Command "pwsh -workingdirectory \"HKLM:\SOFTWARE\WOW6432Node\Cisco\Cisco AnyConnect Secure Mobility Client\\\" -c \"get-itemproperty .\""
Debug: Elevating process: C:\ProgramData\chocolatey\lib\gsudo\bin\gsudo.exe --debug gsudoelevate --pid 22604
Debug: Elevated instance started.

VPNClientInstalled   : 1
InstallPathWithSlash : C:\Program Files (x86)\Cisco\Cisco AnyConnect Secure Mobility Client\
Full Install         : on
SuppressModalDialogs : 1
PSPath               : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Cisco\Cisco AnyConnect Secure Mobility Client\
PSParentPath         : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Cisco
PSChildName          : Cisco AnyConnect Secure Mobility Client
PSDrive              : HKLM
PSProvider           : Microsoft.PowerShell.Core\Registry


Debug: Process exited with code 0

@gerardog
Copy link
Owner

Hi Matt.

Additionally, after typing this up I am noticing it is most likely a powershell issue?

No, it is a problem on the pwsh <-> gsudo interaction. Do not expect that all the pwsh context is forwarded to the elevated command, because it runs in a separated process.

Would it be reasonable to check if current running process was pwsh.exe and then use the aforementioned parameter?

Reasonable, yes. Posible? Not so much.

Powershell's ability to set the "current location" to something other than a file system folder is a PowerShell fabrication, not something supported by the operating system itself. Gsudo is a OS application that even when called from pwsh, even when it (already) knows it is called from pwsh, is still a separate process and can't possibly know whatever the parent process has internally set as the 'current location'. It's not (in this case) the OS's file system current directory for sure (which is something gsudo can and does know thanks to the OS), and gsudo cannot drill down into another process (the pwsh caller) to find what was it's 'current location'.

If gsudo were a native powershell command (or had a native ps1 wrapper #39), there would be more options available.

In a nutshell, gsudo can't know what powershell's Get-Location is. It only knows [System.Environment]::CurrentDirectory, but that's not exactly the same thing. Therefore gsudo will have unexpected results if invoked from a non-FileSystem Provider's location.

And AFAICT, can't be fixed, unless we dive into #39.

Workarounds:

  • Elevate commands that dont require context.
    • Use full paths like $PWD or 'HKLM:\xxx' instead of relative paths. gsudo get-item hklm:\software
    • Embed the context in the command itself gsudo 'Set-Location hklm:\software ; get-item .'

Sorry.

@gerardog gerardog changed the title Issue: Registry Provider *ItemProperty Cmdlets Have Wrong Path Issue: Setting PowerShell's location using any provider other than FileSystem is not supported. Jan 16, 2022
@gerardog gerardog changed the title Issue: Setting PowerShell's location using any provider other than FileSystem is not supported. Issue: Setting PowerShell's location to any provider other than FileSystem is not supported. Jan 16, 2022
@mattcargile
Copy link
Author

Ah, I see. Thanks for the robust explanation! Very enlightening.

I had looked at Issue #39. It would be awesome to get that going more so.

On kind of a different note sparked from reading the docs, it would be cool if gsudo could return the clixml using the OutputFormat parameter. Not sure if you had ever explored that.

Anyway, love the project. Thanks for the help. Feel free to close the Issue or I'll close if you have anything more to add.

@gerardog
Copy link
Owner

On kind of a different note sparked from reading the docs, it would be cool if gsudo could return the clixml using the OutputFormat parameter. Not sure if you had ever explored that.

I've tried it, but powershell does not deserealizes the results automatically:

(For faster testing, I am using gsudo -d pwsh -outputformat xml ... syntax instead of modifying gsudo to include -outputformat)

PS C:\Users\gerar> gsudo -d pwsh -outputformat xml -c "1+1"
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><I32 S="Output">2</I32></Objs>
PS C:\Users\gerar>

which means, the user can not simply prepend gsudo to elevate, and instead must change the syntax to something like:

> [System.Management.Automation.PSSerializer]::Deserialize((gsudo (...) "$command" ))

and this syntax is awfull, and I can't do anything inside gsudo.exe to fix this.
But instead, you can simply use the unaltered gsudo, and leave the de/serialization to the user, as shown in the readme:

# Cross-boundary object serialization example.
$command = 'Get-Item "C:\My Secured Folder\My Secret.txt"'
$result = [System.Management.Automation.PSSerializer]::Deserialize((gsudo "[System.Management.Automation.PSSerializer]::Serialize(( $command ))" ))
Write-Host $result.CreationTime

Now it should be time to focus on #39. There is experimental Invoke-gsudo.ps1, file which should be on the path, that allows you to do things like:

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

There is room in Invoke-gsudo.ps1 to handle Set-Location to any location other than FileSystem-based.

I will try to get back into #39 and make it move forward soon.

@mattcargile
Copy link
Author

mattcargile commented Jan 24, 2022

I gotcha. That is unfortunate and ugly syntax. Thanks for the work on the PS function!

I was playing with it a little. It was weird that setting the below to a variable actually invokes the serialization on pwsh.exe, 7.2.1.

So the below returns the raw CLIXML

gsudo -d pwsh -outputformat xml -c "gci | select -first 1"

while the below holds the deserialized object. It is like in the setting to a variable the serialization is forced to happen.

$result = gsudo -d pwsh -outputformat xml -c "gci | select -first 1"; $result.Name

I was also playing with the below.

[System.Management.Automation.PSSerializer]::Deserialize( "$(gsudo -d pwsh -outputformat xml -c "1+1")" )

It returned an error though. I guess that's why you had to add a Serialize to the $command

PSMessageDetails      :
Exception             : System.Management.Automation.MethodInvocationException: Exception calling "Deserialize" with "1" argument(s): "Data at the root level is invalid.
                        Line 1, position 1."
                         ---> System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1.
                           at System.Xml.XmlTextReaderImpl.Throw(Exception e)
                           at System.Xml.XmlTextReaderImpl.Throw(String res, String arg)
                           at System.Xml.XmlTextReaderImpl.ParseRootLevelWhitespace()
                           at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
                           at System.Management.Automation.Deserializer.Start()
                           at System.Management.Automation.Deserializer..ctor(XmlReader reader, DeserializationContext context)
                           at System.Management.Automation.PSSerializer.DeserializeAsList(String source)
                           at System.Management.Automation.PSSerializer.Deserialize(String source)
                           at CallSite.Target(Closure , CallSite , Type , String )
                           --- End of inner exception stack trace ---
                           at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception exception, Type typeToThrow, String
                        methodName, Int32 numArgs, MemberInfo memberInfo)
                           at CallSite.Target(Closure , CallSite , Type , String )
                           at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
                           at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
                           at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          :
CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : XmlException
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}

I see now a little bit that there is a difference between the CLIXML returned and the XML returned when you use the Serialize and Deserialize methods. In the setting of the variable, I guess a form of Import-Clixml is used to collect the data while the Serialize/Deserialize methods are different.

Thanks for the discussion and help. I appreciate the long responses.

EDIT 1: I see a little more now. In the output, when it has #< CLIXML, the Deserialize method doesn't like that for some reason.

@gerardog
Copy link
Owner

I think #< CLIXML response announces that a xml structure with a list of serialized objects follows. Each item could be deserialized with [PSSerializer]::Deserialize(), but no point in doing that manually if it's built into Pwsh, like: if you do -outputformat xml then just capture into $result =... (and asume a list is received).

I prefer if you could experiment with 'Invoke-gsudo.ps1' where I've already worked thru several issues. I've just pushed a new version. I will announce it as open-for-testing on #39, but i'm delayed in an eternal loop of testing / debugging / and powershell A-ha moments.

For example for some reason

  • Invoke-gsudo { return Get-Content 'C:\Test'} returns the file content
  • Invoke-gsudo { Get-Content 'C:\Test' ; 1} returns the content and a 1
  • Invoke-gsudo { Get-Content 'C:\Test'} returns nothing

In a nutshell, my goal is that Invoke-gsudo behaves as similar as possible to Invoke-Command (but elevated). Obviously will never be equal since Invoke-gsudo runs in a different scope / process. I hope you get what I mean.

If you are open to experimentation, please grab it and place it on gsudo's folder. Let me know how it goes.

BTW, Set-Location HKLM:\SOFTWARE ; Invoke-gsudo { return Get-Item . } works now!

@mattcargile
Copy link
Author

Excellent! That is awesome!

I'll continue to test the ps1 file on that Feature.PwshSyntax branch. I just wrapped the code in a function and copied it into my $PROFILE so that I could dotsource it.

@gerardog
Copy link
Owner

Hey, since you brought the dotsource idea, let me ask you a big question. This is my first time releasing powershell code to the public, and I'm definitely not familiar on how to 'package' it.

Releasing gsudo is automated but still hard because it needs testing for scoop / chocolatey / winget .MSI / manual installation / etc. Testing fresh install, upgrading from diferent versions...

So I wanted to keep the 'deployment model' for this Powershell function as simple as possible.

  • Adding Invoke-gsudo.ps1 to gsudo folder (which is in the path) seemed ideal to me, as it was 'package-manager-independent'.
  • Creating a powershell module sounds like nightmare. People would have to install gsudo and then the PSM. 2 different packages, which could get version mismatch. Or else include the binary inside the PSM, but then you have a binary from the PSM and one from chocolatey. All trouble AFAICT.

I settled with the current approach... so let me ask... Did you had to wrap the .ps1 content with function Invoke-gsudo { (...) } ?
If so, why dotsource it? Does it feel better to have Invoke-gsudo as function?

Do you imagine any other way of releasing a Invoke-gsudo function (easiest as possible to implement on all package managers)?

Thanks!

@mattcargile
Copy link
Author

Yeah I see what you mean now that I'm thinking about it more. I was quickly trying to incorporate it into my $PROFILE. Yes, you are correct, I simply wrapped the .ps1 content in the function block. It is typical of how I see PowerShell and how I write my own functions. It is nicer to Invoke-Gs + <TAB> and have it not have a .ps1 on the end. It is really negligible though.

OhMyPosh deploys a dual approach on winget and PSGallery. The PowerShell module ( after you install the module ) installs the binary into a single location that all versions of the PowerShell module use, if I remember right. (NOTE: There were sizing concerns with all the different binaries. ) OhMyPosh started as a PSGallery module if I remember right, so I see why they went with this dual approach.

Users don't come to your application through the PSGallery anyway. If there was a module, I could see it as a separate repo where the underlying code is deployed as a library that a Powershell module could use instead of calling the exe within PowerShell. Then again, how many functions are really possible with the feature set? One could create a separate Powershell Module similar to this folder and then there is a function inside that reaches out to grab the .exe. This would be more of a feature request and would be created to try to reach more people which, again, is probably not relevant to this discussion anyway. I guess the main benefit of the one of these approaches is that Invoke-gsudo.ps1 may be more discoverable within the PSGallery but yeah then we would still need a way to get the script/module out to the other scoop, choco, etc users.

All that being considered, I think the approach you have outlined is the best. I copied your recent build into my "$env:ChocolateyInstall\lib\gsudo\bin\" so I'll be testing it. And I may completely switch to Invoke-gsudo.ps1, if I can come up with a decent alias. sudo is already short and memorable, it may be hard for me to adopt this new Invoke-gsudo.ps1 without me coming up with a longer alias like isudo or the like.

@mattcargile mattcargile reopened this Jan 24, 2022
@gerardog
Copy link
Owner

Hi Matt,
I really appreciate you taking the time to help and all the valuable info!

The idea of maybe of adding an additional .psm1 file that you can include in your profile is growing on me. Import-Profile 'C:\FullPathToGsudo\gsudo.psm1'. Would the module duplicate the code of Invoke-gsudo.ps1? Maybe I could dotsource it? Would try. Possibly next week.

Regarding features that the module could have:

  • there is gsudo !! sudo !! (bang bang) for PowerShell #44, to re-run last command with elevated privileges.. But, as the command would run in a different scope, this will confuse people. People reporting user-problems as issues, etc. So there is that, and on the other hand also people requesting it.
  • Maybe an Invoke-Expression equivalent?, but also I haven't decided yet if it serves any purpose. It would receive a string instead of an ScriptBlock. In pseudocode may be something like function Invoke-GsudoExpression (x) => Invoke-gsudo { Invoke-Expression x } accepting $using:variableName syntax. Here I need user feedback on how they use gsudo on pwsh. I assume people rewrite the statement to elevate it, would Invoke-GsudoExpression help or not?
  • Maybe adding an overload Invoke-Command -Elevated that forwards to Invoke-Gsudo ?

I'm thinking out loud here.

BTW, sudo is the alias I would go with once I settled to use the ScriptBlock syntax. I wouldnt create the alias in the installers. The user would want to do that, i think.

@mattcargile
Copy link
Author

mattcargile commented Jan 24, 2022

Oh yeah, I like the idea of having the psm1!

I would think it should replace the Invoke-gsudo.ps1 though. It might be confusing to have both the psm1 file and ps1 file in the same $env:Path directory.

If you wanted to have both, I would think some type of Get-Content Invoke-gsudo.ps1 would have to be created to grab the file and then wrap it in function { } to be dot sourced in the psm1. Then someone who has imported the module will have both Invoke-gsudo.ps1 and Invoke-gsudo within their session ( i.e. Get-Command Invoke-gsudo* ).

Other examples include:
fd.exe packages a ps1 which must be dot sourced
choco.exe packages a psm1 file. ( NOTE: Benefits include being able to remove the module with Remove-Module. )

Now the features you mentioned are very interesting. I didn't even know about the !! deal; that would be a great feature in the module! Then the Invoke-Command would be next for usefulness followed by the Invoke-Expression wrapper.

I like the idea of the proxy command for Invoke-Command mentioned in this comment. Invoke-Command has many, many parameters though so it may be a challenge to manage. It would probably loop in well with some folks' muscle memory because I like the icm alias and it would be nice to do icm { get-pssessionconfiguration} -elevate. This implementation was discussed in this Powershell issue that you were in too.

For Invoke-Expression, I've been trying to think about how I would use it. Maybe if I had some admin command and I needed to use pwsh.exe to generate the pwsh code it would prove useful. I guess the wrapper could help with some of the quoting nightmares to make it more useful.

Lastly, I do like the idea of the users generating their own alias. I would think many PowerShell users would import the module and then alias the Invoke-Gsudo function to sudo. It would be as easy to alias gsudo.exe to sudo as well. I guess it would be less convenient for the cmd.exe users as they would have to create their own symbolic link. Then uninstalls may be more cumbersome as the uninstaller wouldn't expect that sudo.exe symbolic link file in the directory? I would assume those primary cmd.exe users are few and far between. I use cmd.exe when I have to uninstall older versions of powershell modules. I would reference gsudo.exe in those cases.

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

2 participants