Issue: Invoke-Gsudo so slow it appears to hang
MathiasMagnus opened this issue · 4 comments
Issue Description
I was giving gsudo my first spin, and I don't know if I'm doing something wrong, or is the brave new 1.7.1 is broken.
Steps to Reproduce
PS C:\Users\mate> gcm sudo
CommandType Name Version Source
----------- ---- ------- ------
Alias sudo -> Invoke-Gsudo
PS C:\Users\mate> gmo gsudoModule
ModuleType Version PreRelease Name ExportedCommands
---------- ------- ---------- ---- ----------------
Script 0.0 gsudoModule {gsudo, Invoke-Gsudo}
PS C:\Users\mate> gmo gsudoModule | select -exp Path
C:\Program Files (x86)\gsudo\gsudoModule.psm1
PS C:\Users\mate> Get-Process -IncludeUserName
Get-Process: The 'IncludeUserName' parameter requires elevated user rights. Try running the command again in a session that has been opened with elevated user rights (that is, Run as Administrator).
PS C:\Users\mate> sudo -Debug { Get-Process -IncludeUserName }
DEBUG: User ScriptBlock : Get-Process -IncludeUserName
DEBUG: Full Script to run on the isolated instance: {
$InputObject = $([System.Management.Automation.PSSerializer]::Deserialize([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PE9ianMgVmVyc2lvbj0iMS4xLjAuMSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vcG93ZXJzaGVsbC8yMDA0LzA0Ij4NCiAgPE9iaiBSZWZJZD0iMCI+DQogICAgPFROIFJlZklkPSIwIj4NCiAgICAgIDxUPlN5c3RlbS5PYmplY3RbXTwvVD4NCiAgICAgIDxUPlN5c3RlbS5BcnJheTwvVD4NCiAgICAgIDxUPlN5c3RlbS5PYmplY3Q8L1Q+DQogICAgPC9UTj4NCiAgICA8TFNUIC8+DQogIDwvT2JqPg0KPC9PYmpzPg=='))));
$argumentList = $null;
$expectingInput = $([System.Management.Automation.PSSerializer]::Deserialize([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PE9ianMgVmVyc2lvbj0iMS4xLjAuMSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vcG93ZXJzaGVsbC8yMDA0LzA0Ij4NCiAgPEI+ZmFsc2U8L0I+DQo8L09ianM+'))));
$sb = [Scriptblock]::Create($([System.Management.Automation.PSSerializer]::Deserialize([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PE9ianMgVmVyc2lvbj0iMS4xLjAuMSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vcG93ZXJzaGVsbC8yMDA0LzA0Ij4NCiAgPFM+IEdldC1Qcm9jZXNzIC1JbmNsdWRlVXNlck5hbWUgPC9TPg0KPC9PYmpzPg==')))));
Set-Location $([System.Management.Automation.PSSerializer]::Deserialize([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PE9ianMgVmVyc2lvbj0iMS4xLjAuMSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vcG93ZXJzaGVsbC8yMDA0LzA0Ij4NCiAgPFM+QzpcVXNlcnNcbWF0ZTwvUz4NCjwvT2Jqcz4='))));
$ErrorActionPreference=$([System.Management.Automation.PSSerializer]::Deserialize([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PE9ianMgVmVyc2lvbj0iMS4xLjAuMSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vcG93ZXJzaGVsbC8yMDA0LzA0Ij4NCiAgPFM+Q29udGludWVfeDAwMERfX3gwMDBBXzwvUz4NCjwvT2Jqcz4='))));
if ($expectingInput) {
try {
($InputObject | Invoke-Command $sb -ArgumentList $argumentList)
} catch {throw $_}
} else {
try{
(Invoke-Command $sb -ArgumentList $argumentList)
} catch {throw $_}
}
}
And the cursor just stands still for eternity.
Context:
PS C:\Users\mate> Get-ComputerInfo | ft -Property OsName,OsVersion,OsLanguage
OsName OsVersion OsLanguage
------ --------- ----------
Microsoft Windows 11 Pro 10.0.22621 hu-HU
PS C:\Users\mate> gsudo --version
gsudo v1.7.1 (Branch.tags-v1.7.1.Sha.12438bbef9a72e857840401995fc0317ea99c3b3)
Copyright(c) 2019-2022 Gerardo Grignoli and GitHub contributors
Hi,
It is not hang, It is just too slow serializing and deserializing all the process tree, which is a relatively big object graph. If you leave enough time, it will finish.
You can try this experiment without involving gsudo. Open a Pwsh
console and in the powershell prompt run exactly pwsh {Get-Process}
.
It will probably take less than 2 seconds, because Pwsh is just serializing the text output. Now run exactly (pwsh {Get-Process})
. The extra ()
will force a full object serialization & deserealization, which can easily take 100 seconds.
❯ pwsh {Get-Process}
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
(...)
gerar C:\git\gsudo master ⇡ 3 37 1.31s <== 1.31 seconds...
❯
VS
❯ (pwsh {Get-Process})
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
gerar C:\git\gsudo master ⇡ 3 37 1m 35.211s <== 95 seconds...
❯
Currently, Invoke-gsudo is always using a manual version of full serialization and deserialization, which results slow for this command.
❯ invoke-gsudo {Get-Process}
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
(...)
gerar C:\git\gsudo master ⇡ 3 37 2m 6.571s
❯
A month ago with the v1.6.0 release, a new syntax was added to gsudo: gsudo {scriptblock}
(instead of Invoke-gsudo). This syntax took advantage of Pwsh's own serialization method. and leverages pwsh magic trick of only serializing the text output (instead of the full object graph) if the caller is not trying to capture the output (e.g. no parentheses) which is very fast, and transparently transition to full serialization only if needed.
You may need to remove your sudo alias, or point it to gsudo
(instead of invoke-gsudo) to use this.
Let's see how it performs
❯ gsudo.exe {Get-Process -IncludeUserName}
WS(M) CPU(s) Id UserName ProcessName
----- ------ -- -------- -----------
gerar C:\git\gsudo master ⇡ 3 37 2.607s <== not bad!
❯
But with ()
❯ (gsudo.exe {Get-Process -IncludeUserName})
WS(M) CPU(s) Id UserName ProcessName
----- ------ -- -------- -----------
(...)
gerar C:\git\gsudo master ⇡ 3 37 2m 12.125s <== at least it works. lol
❯
TL;DR
- Powershell suffers serializing big object graphs, and gsudo feels the pain.
- In some situations
gsudo {ScriptBlock}
is much much faster thanInvoke-gsudo {ScriptBlock}
.
Not sure how can I improve the situation. Maybe improve the docs? Suggestions welcomed.
Thanks @gerardog for the throrough reply.
I think there are two aspects to improving the situation:
- Educate people on the status quo, why things are the way they are and why this is the state of the art. (Most of the root causes are historic, and it's not like Windows admins have had to suffer for decades... it's just asking the Windows security model to do something which it wasn't designed to do.) gsudo's README is a good place for that. Perhaps even a link to the related PS issue.
- Improving the current bottlenecks to allow more *nix-style administering, elevating per-command as needed. Improving object serialization/deserialization is an obvious first to investigate.
You've already done a tremendous job, gsudo is mighty useful, but IMHO we're still not in cross-plat admin nirvana.
gps -inc | where -prop UserName -Match $env:USERNAME
is an established muscle/mind reflex equivalent tops aux | grep $LOGNAME
and committing the mistake of accidentally issuing such a command and it blocks the terminal for a solid minute... it just means there's a constant background process of "can I use sudo here?" running in our mind which is just unnecessary mental burden.
- Educate people on the status quo, why things are the way they are and why this is the state of the art.
Take a look at the modifications in:
- https://github.com/gerardog/gsudo#usage-from-powershell--powershell-core (view diff)
- https://gerardog.github.io/gsudo/docs/usage/powershell or 4752e60
- Improving the current bottlenecks...
I wish I had the time or resources to improve PowerShell serialization.
You've already done a tremendous job, gsudo is mighty useful, but IMHO we're still not in cross-plat admin nirvana.
gps -inc | where -prop UserName -Match $env:USERNAME
is an established muscle/mind reflex equivalent tops aux | grep $LOGNAME
Thanks! BTW, you can still do:
gsudo {gps -inc | where -prop UserName -Match $env:USERNAME}
.
The filter will run before serialization, so the result is narrowed first, making the response faster.
I'm closing this issue now, but please don't hesitate to reply with any suggestions.
Thanks