Skip to content

Commit

Permalink
Updated version to v0.5. Unit tests improvements but sill WIP.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerardo Grignoli committed Dec 31, 2019
1 parent bbfe355 commit 3b39515
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 56 deletions.
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ scoop install gsudo
choco install gsudo --version=0.4.1
```

Note: You can use the `gsudo` command or the `sudo` alias anywhere, whatever you like the most. The alias is created automatically by both Scoop and Chocolatey installers.

Manual installation:

Download the [latest release](https://github.com/gerardog/gsudo/releases/latest). Unzip to a local folder. Then either add it to the path or you can alias the `sudo` keyword to `gsudo` with:
`mklink "C:\windows\system32\sudo.exe" "C:\FullPathTo\gsudo.exe"`.

## Usage

Note: You can use anywhere the `gsudo` command or the `sudo` alias created by `Scoop` or `Chocolatey` installers.

```gsudo```
Opens an elevated shell in the current console.

Expand All @@ -52,6 +52,24 @@ Show current-user settings.
```gsudo config {key} [value]```
Read or write a user setting

## Usage from PowerShell

`gsudo` detects if it is invoked from a PowerShell shell and allows the following syntax to elevate PS commands.

`PS C:\> gsudo 'powershell string command'`

Examples:

``` PowerShell
# Escape " with ""
gsudo 'Get-FileHash "".\My Secret.txt""'
# String values can be replaced at the string level.
# Variables and Objects not shared between elevated and not elevated sessions.
gsudo "Get-FileHash "".\My Secret.txt"" -Algorithm $algorithm"
```

## Demo

![gsudo demo](demo.gif)
Expand All @@ -61,28 +79,29 @@ Read or write a user setting
- Elevated commands are shown in the user-level console, as `*nix sudo` does, instead of opening the command in a new window.
- Credentials cache: If `gsudo` is invoked several times within minutes it only shows the UAC pop-up once.
- Suport for CMD commands: `gsudo md folder` (no need to use the longer form `gsudo cmd.exe /c md folder`
- <kbd>Ctrl</kbd>+<kbd>C</kbd> key press is correctly forwarded to the elevated process. (eg. cmd/powershell won't die, but ping/nslookup/batch file will.
- Suport for PowerShell commands if invoked from a PS shell.
- <kbd>Ctrl</kbd>+<kbd>C</kbd> key press is correctly forwarded to the elevated process. (eg. cmd/powershell won't die, but ping/nslookup/batch file will.
- Scripting:
- `gsudo` can be used on scripts that requires to elevate one or more commands. (the UAC popup will appear once).
- Outputs and exit codes of the elevated commands can be interpreted: E.g. StdOutbound can be piped or captured (`gsudo dir | findstr /c:"bytes free" > FreeSpace.txt`) and exit codes too ('%errorlevel%)).
- If `gsudo` is invoked (with params) from an already elevated console it will just run the commands. So if you invoke a script that uses `gsudo` from an already elevated console, it will also work. The UAC popup would not appear.

## Known issues

- Please report issues in the [Issues](https://github.com/gerardog/gsudo/issues) section.
- Feel free to contact me at gerardog @at@ gmail.com
- This project is a work in progress. Many improvements in the [backlog](backlog.md).
- `Scoop` shim messes [CTRL-C behaviour](https://github.com/lukesampson/scoop/issues/1896). ([Upvote this!](https://github.com/lukesampson/scoop/issues/3634)) `Chocolatey` not afected, because as Choco installs elevated I could change the install to create symbolic links instead of shims.
- Please report issues in the [Issues](https://github.com/gerardog/gsudo/issues) section or contact me at gerardog @at@ gmail.com
- This project is a work in progress. Many improvements in the [backlog](backlog.md).

## FAQ

- Why `gsudo` instead of just `sudo`?
- Why `gsudo` instead of just `sudo`?

When I created `gsudo`, there were other `sudo` packages on most Windows popular package managers such as `Chocolatey` and `Scoop`, so I had no other choice to pick another name. `gsudo` installers on Scoop and Chocolatey create aliases for `sudo`, so feel free to use the `sudo` alias instead on your command line to invoke `gsudo`.

- Why `.Net Framework 4.6`?

Because 4.6 is included in every Windows 10 installation. Also avoided `.Net Core` because gsudo is Windows-specific, (other platforms can use the standard *nix sudo.)
Because 4.6 is included in every Windows 10 installation. Also avoided `.Net Core` because gsudo is Windows-specific, (other platforms can use the standard *nix sudo.)

- Want to know more?
- Want to know more?

Check the [internals](internals.md) page.
Check the [internals](internals.md) page.
2 changes: 0 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ image: Visual Studio 2019

init:
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
- git config --global core.autocrlf input
# - ps: '(Get-ChildItem -Path "Env:")'

before_build:
- nuget restore src\gsudo.sln
Expand Down
2 changes: 1 addition & 1 deletion src/Scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
gsudo/tools/*
gsudo.nuspec
Chocolatey/gsudo/tools/*
39 changes: 25 additions & 14 deletions src/Scripts/Build.bat
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
@pushd %~dp0
@if 'a'=='a%1' echo Missing version number
@if 'a'=='a%1' goto end
@if 'a%msbuild%' == 'a' set msbuild="C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
@if ''=='%1' echo Missing version number
@if ''=='%1' goto end
@if '%msbuild%' == '' set msbuild="C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
@if '%SignToolPath%' == '' set SignToolPath="C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\"

@echo Building with version number v%1

del ..\gsudo\bin\*.* /q
%msbuild% /t:Restore,Rebuild /p:Configuration=Release /p:WarningLevel=0 %~dp0..\gsudo.sln

pause "Please sign gsudo\bin\*.exe"
if not errorlevel 0 goto badend

%SignToolPath%signtool.exe sign /n "Open Source Developer, Gerardo Grignoli" /fd SHA256 /tr "http://time.certum.pl" ..\gsudo\bin\gsudo.exe

if not errorlevel 0 goto badend

7z a Releases\gsudo.v%1.zip ..\gsudo\bin\*.*
powershell (Get-FileHash Releases\gsudo.v%1.zip).hash > Releases\gsudo.v%1.zip.sha256

copy ..\gsudo\bin\*.* %~dp0\Chocolatey\gsudo\Tools
:: Chocolatey
copy %~dp0\..\gsudo\bin\*.* %~dp0\Chocolatey\gsudo\Tools
copy Chocolatey\verification.txt.template Chocolatey\gsudo\Tools\VERIFICATION.txt
@pushd %~dp0\Chocolatey\gsudo
powershell -NoProfile -Command "(gc gsudo.nuspec.template) -replace '#VERSION#', '%1' | Out-File -encoding UTF8 gsudo.nuspec"
echo --- >> tools\verification.txt
echo Version Hashes for v%1 >> tools\verification.txt
echo. >> tools\verification.txt
powershell Get-FileHash tools\*.* >> tools\verification.txt
echo. >> tools\verification.txt
cd ..
choco pack gsudo\gsudo.nuspec -outdir="%~dp0\Releases"

@pushd %~dp0\Chocolatey\gsudo
powershell -NoProfile -Command "(gc gsudo.nuspec.template) -replace '#VERSION#', '%1' | Out-File -encoding UTF8 gsudo.nuspec"
echo --- >> tools\verification.txt
echo Version Hashes for v%1 >> tools\verification.txt
echo. >> tools\verification.txt
powershell Get-FileHash tools\*.* >> tools\verification.txt
echo. >> tools\verification.txt
cd ..
choco pack gsudo\gsudo.nuspec -outdir="%~dp0\Releases"
@popd

goto end
:badend
exit /b 1
:end
@popd
8 changes: 8 additions & 0 deletions src/Scripts/UploadToGitHub.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@pushd %~dp0\Releases
@if 'a'=='a%1' echo Missing version number
@if 'a'=='a%1' goto end
@echo Building with version number v%1
::scoop install hub
hub release create -d -a gsudo.v%1.zip -a gsudo.v%1.zip.sha256 -m "gsudo v%1" v%1
:end
@popd
70 changes: 48 additions & 22 deletions src/gsudo.Tests/CmdTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using gsudo.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand All @@ -23,46 +24,75 @@ public void Cmd_AdminUserTest()
[TestMethod]
public void Cmd_DirTest()
{
var p = new TestProcess("gsudo", "--debug cmd /c dir");
var p = new TestProcess("gsudo.exe", "--debug cmd /c dir");
p.WaitForExit();
Assert.AreEqual(string.Empty, p.GetStdErr());
Assert.IsTrue(p.GetStdOut().Contains(" bytes free"));
Assert.AreEqual(0, p.Process.ExitCode);
Assert.AreEqual(0, p.ExitCode);
}

[TestMethod]
public void Cmd_ChangeDirTest()
{
// TODO: Test --raw, --vt, --attached
var testDir = Environment.CurrentDirectory;
var p1 = new TestProcess("gsudo.exe", "--debug cmd /c cd");
p1.WaitForExit();

var otherDir = Path.GetDirectoryName(Path.Combine(Environment.CurrentDirectory,".."));
Environment.CurrentDirectory = otherDir;

Assert.AreEqual(string.Empty, p1.GetStdErr());
Assert.AreEqual($"{testDir}\r\n", p1.GetStdOut());
Assert.AreEqual(0, p1.ExitCode);

try
{
var p2 = new TestProcess(Path.Combine(testDir, "gsudo.exe"), "--debug cmd /c cd");
p2.WaitForExit();

Assert.AreEqual(string.Empty, p2.GetStdErr());
Assert.AreEqual($"{otherDir}\r\n", p2.GetStdOut());
Assert.AreEqual(0, p2.ExitCode);
}
finally
{
Environment.CurrentDirectory = testDir;
}
}
[TestMethod]
public void Cmd_EchoDoubleQuotesTest()
{
var p = new TestProcess("gsudo", "cmd /c echo 1 \"2 3\"");
var p = new TestProcess("gsudo.exe", "cmd /c echo 1 \"2 3\"");
p.WaitForExit();
Assert.AreEqual("1 \"2 3\" \r\n", p.GetStdOut());
Assert.AreEqual(0, p.Process.ExitCode);
Assert.AreEqual(0, p.ExitCode);
}

[TestMethod]
public void Cmd_EchoSimpleQuotesTest()
{
var p = new TestProcess("gsudo", "cmd /c echo 1 \'2 3\'");
var p = new TestProcess("gsudo.exe", "cmd /c echo 1 \'2 3\'");
p.WaitForExit();
Assert.AreEqual("1 \'2 3\' \r\n", p.GetStdOut());
Assert.AreEqual(0, p.Process.ExitCode);
Assert.AreEqual(0, p.ExitCode);
}

[TestMethod]
public void Cmd_ExitCodeTest()
{
var p = new TestProcess("gsudo", "exit /b 12345");
var p = new TestProcess("gsudo.exe", "exit /b 12345");
p.WaitForExit();
Assert.AreEqual(string.Empty, p.GetStdErr());
Assert.AreEqual(string.Empty, p.GetStdOut());
Assert.AreEqual(12345, p.Process.ExitCode);
Assert.AreEqual(12345, p.ExitCode);
}

[TestMethod]
public void Cmd_CommandLineAppNoWaitTest()
{
// ping should take 20 seconds
var p = new TestProcess("gsudo", "-n ping 127.0.0.1 -n 20");
var p = new TestProcess("gsudo.exe", "-n ping 127.0.0.1 -n 20");
// but gsudo should exit immediately.
p.WaitForExit(2000);
Assert.AreEqual(string.Empty, p.GetStdOut());
Expand All @@ -72,7 +102,7 @@ public void Cmd_CommandLineAppNoWaitTest()
public void Cmd_WindowsAppWaitTest()
{
bool stillWaiting = false;
var p = new TestProcess("gsudo", "-w notepad");
var p = new TestProcess("gsudo.exe", "-w notepad");
try
{
p.WaitForExit(2000);
Expand All @@ -83,23 +113,22 @@ public void Cmd_WindowsAppWaitTest()
}

Assert.IsTrue(stillWaiting);
Process.Start("taskkill", "/FI \"WINDOWTITLE eq Untitled - Notepad\"").WaitForExit();
p.WaitForExit();
Process.Start("C:\\Windows\\sysnative\\tskill.exe", "notepad").WaitForExit();
Assert.AreEqual(string.Empty, p.GetStdErr());
Assert.AreEqual(string.Empty, p.GetStdOut());
}

[TestMethod]
public void Cmd_WindowsAppNoWaitTest()
{
var p = new TestProcess("gsudo", "calc");
var p = new TestProcess("gsudo.exe", "notepad");
try
{
p.WaitForExit();
}
finally
{
Process.Start("taskkill", "/FI \"WINDOWTITLE eq Calculator\"").WaitForExit();
Process.Start("C:\\Windows\\sysnative\\tskill.exe", "notepad").WaitForExit();
}
Assert.AreEqual(string.Empty, p.GetStdErr());
Assert.AreEqual(string.Empty, p.GetStdOut());
Expand All @@ -108,18 +137,15 @@ public void Cmd_WindowsAppNoWaitTest()
[TestMethod]
public void Cmd_WindowsAppWithQuotesTest()
{
var p = new TestProcess("gsudo", $"\"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
var p = new TestProcess("gsudo.exe", $"\"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
try
{
p.WaitForExit();
Assert.AreEqual(0, p.Process.ExitCode);
Assert.AreEqual(0, p.ExitCode);
}
finally
{
Process.Start("C:\\Windows\\sysnative\\tskill.exe", "wordpad");
//Process.Start("taskkill", "/FI \"WINDOWTITLE eq WordPad\"").WaitForExit();
//Process.Start("taskkill", "/FI \"WINDOWTITLE eq Document - WordPad\"").WaitForExit();

Process.Start("C:\\Windows\\sysnative\\tskill.exe", "wordpad").WaitForExit();
}
Assert.AreEqual(string.Empty, p.GetStdErr());
Assert.AreEqual(string.Empty, p.GetStdOut());
Expand All @@ -128,9 +154,9 @@ public void Cmd_WindowsAppWithQuotesTest()
[TestMethod]
public void Cmd_UnexistentAppTest()
{
var p = new TestProcess("gsudo", "qaqswswdewfwerferfwe");
var p = new TestProcess("gsudo.exe", "qaqswswdewfwerferfwe");
p.WaitForExit();
Assert.AreNotEqual(0, p.Process.ExitCode);
Assert.AreNotEqual(0, p.ExitCode);
}
}
}
20 changes: 15 additions & 5 deletions src/gsudo.Tests/PowershellTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,23 @@ public void PS_CommandLineEchoDoubleQuotesTest()
Assert.AreEqual(0, p.Process.ExitCode);
}

[TestMethod]
public void PS_EchoNoQuotesTest()
{
var p = new TestProcess("powershell", string.Empty);
p.WriteInput("gsudo 'echo 1 2 3'\r\n");
System.Threading.Thread.Sleep(20000);
p.WriteInput("exit\r\n");
Assert.AreEqual("1\r\n2\r\n3\r\n", p.GetStdOut());
Assert.AreEqual(0, p.Process.ExitCode);
}

[TestMethod]
public void PS_EchoSingleQuotesTest()
{
var p = new TestProcess("powershell", string.Empty);

p.WriteInput("gsudo 'echo 1 \"2 3\"'");
p.WriteInput("gsudo 'echo 1 \"2 3\"'\r\nexit\r\n");
p.WaitForExit();
Assert.AreEqual("1\r\n2 3\r\n", p.GetStdOut());
Assert.AreEqual(0, p.Process.ExitCode);
Expand All @@ -42,10 +53,9 @@ public void PS_EchoSingleQuotesTest()
[TestMethod]
public void PS_EchoDoubleQuotesTest()
{
var p = new TestProcess("powershell", string.Empty);

p.WriteInput("gsudo 'echo 1 \"2 3\"'");
p.WaitForExit();
var p = new TestProcess("cmd", string.Empty);
p.WriteInput("gsudo 'echo 1 \"2 3\"'\r\nexit\r\n");
//p.WaitForExit();
Assert.AreEqual("1\r\n2 3\r\n", p.GetStdOut());
Assert.AreEqual(0, p.Process.ExitCode);
}
Expand Down
5 changes: 4 additions & 1 deletion src/gsudo.Tests/TestProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace gsudo.Tests
class TestProcess
{
public Process Process { get; private set; }
public int ExitCode => Process.ExitCode;

Stream InputStrem = null;
string _StdErrFileName;
string _StdOutFileName;
Expand All @@ -24,7 +26,8 @@ public TestProcess(string exename, string arguments)
Arguments = $"{arguments} 1> \"{_StdOutFileName}\" 2> \"{_StdErrFileName}\"",
RedirectStandardInput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Minimized
WindowStyle = ProcessWindowStyle.Minimized,
CreateNoWindow=false
};
this.Process.Start();

Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/gsudo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ It is a sudo equivalent for Windows, with a similar user-experience as the origi
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<Version>0.4.3</Version>
<Version>0.5</Version>
<Copyright>2019 Gerardo Grignoli</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/gerardog/gsudo</PackageProjectUrl>
Expand Down

0 comments on commit 3b39515

Please sign in to comment.