├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── release.yml │ └── winget-solo.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── build ├── .gitignore ├── 00-prerequisites.ps1 ├── 01-build.ps1 ├── 02-test.ps1 ├── 03-sign.ps1 ├── 04-build-installer.ps1 ├── 05-release-Chocolatey.ps1 ├── 06-release-Nuget.ps1 ├── Chocolatey │ ├── ReadMe.md │ ├── VERIFICATION.txt.template │ ├── gsudo.nuspec.template │ └── gsudo │ │ ├── Bin │ │ ├── gsudo.exe.ignore │ │ └── sudo.exe.ignore │ │ └── tools │ │ ├── LICENSE.txt │ │ ├── Uninstall-ChocolateyPath.psm1 │ │ ├── chocolateybeforemodify.ps1 │ │ ├── chocolateyinstall.ps1 │ │ └── chocolateyuninstall.ps1 ├── DemoGif │ ├── demo.ahk │ └── demo.bat ├── Nuget │ ├── gsudo.csproj │ └── gsudo.targets └── Scoop │ └── gsudo.json ├── demo.gif ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2021-08-26-welcome │ │ └── docusaurus-plushie-banner.jpeg │ ├── 2022-08-21-dotnet7 │ │ └── index.md │ └── authors.yml ├── docs │ ├── credentials-cache.md │ ├── gsudo-vs-sudo.md │ ├── how-it-works.md │ ├── install.md │ ├── intro.md │ ├── security.md │ ├── tips │ │ ├── _category_.json │ │ ├── elevation-in-new-window.md │ │ ├── mixed-elevation-in-windows-terminal.md │ │ └── script-self-elevation.md │ ├── troubleshooting.md │ └── usage │ │ ├── _category_.json │ │ ├── mingw-msys2.md │ │ ├── powershell.md │ │ ├── usage.md │ │ └── wsl.md ├── docusaurus.config.js ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ ├── markdown-page.md │ │ └── sponsor.md └── static │ ├── .nojekyll │ └── img │ ├── AnimatedPrompt.gif │ ├── NewWindow.CloseBehaviour.KeepShellOpen.png │ ├── NewWindow.CloseBehaviour.PressKeyToClose.png │ ├── Vista-UAC.png │ ├── favicon.ico │ ├── gsudo-anim1.png │ ├── gsudo-anim2.png │ ├── gsudo-powershell-prompt.gif │ ├── gsudo-powershell-prompt.mp4 │ └── gsudo.png ├── installgsudo.ps1 ├── package.json ├── sample-scripts ├── many-elevations-using-gsudo-cache.cmd ├── many-elevations-using-gsudo-cache.ps1 ├── readme.md ├── self-elevate-one-liner.cmd ├── self-elevate-without-gsudo.cmd ├── self-elevate.cmd ├── self-elevate.ps1 ├── silent-elevation-one-liner.cmd └── silent-elevation.cmd ├── src ├── KeyPressTester │ ├── App.config │ ├── KeyPressTester.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── gsudo.Installer.sln ├── gsudo.Installer │ ├── .gitignore │ ├── Constants.Template.wxi │ ├── Product.wxs │ ├── gsudomsi.wixproj │ └── vendor │ │ ├── LICENSE.rtf │ │ └── LICENSE.txt ├── gsudo.Tests │ ├── ArgumentParsingTests.cs │ ├── AssertExtensions.cs │ ├── CmdTests.cs │ ├── CommandLineParserTests.cs │ ├── PowershellTests.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TestProcess.cs │ └── gsudo.Tests.csproj ├── gsudo.Wrappers.Tests │ ├── Invoke-gsudo.Tests.ps1 │ └── gsudo.Tests.ps1 ├── gsudo.Wrappers │ ├── Invoke-ElevatedCommand.ps1 │ ├── Invoke-gsudo.ps1 │ ├── gsudo │ ├── gsudoModule.psd1 │ └── gsudoModule.psm1 ├── gsudo.sln └── gsudo │ ├── AppSettings │ ├── PathPrecedenceSetting.cs │ ├── RegistrySetting.cs │ ├── Settings.cs │ └── Window.cs │ ├── Commands │ ├── BangBangCommand.cs │ ├── CacheCommand.cs │ ├── ConfigCommand.cs │ ├── CtrlCCommand.cs │ ├── HelpCommand.cs │ ├── ICommand.cs │ ├── KillCacheCommand.cs │ ├── RunCommand.cs │ ├── ServiceCommand.cs │ └── StatusCommand.cs │ ├── Constants.cs │ ├── CredentialsCache │ ├── CacheMode.cs │ └── CredentialsCacheLifetimeManager.cs │ ├── ElevationRequest.cs │ ├── Helpers │ ├── ArgumentsHelper.cs │ ├── CommandLineParser.cs │ ├── CommandToRunAdapter.cs │ ├── ConsoleHelper.cs │ ├── ExtensionMethods.cs │ ├── LoginHelper.cs │ ├── ProcessFactory.cs │ ├── ProcessHelper.cs │ ├── SecurityHelper.cs │ ├── ServiceHelper.cs │ ├── ShellHelper.cs │ ├── StringTokenizer.cs │ ├── SymbolicLinkSupport.cs │ ├── TerminalHelper.cs │ └── UACWindowFocusHelper.cs │ ├── InputParameters.cs │ ├── Logger.cs │ ├── Native │ ├── ConsoleApi.cs │ ├── FileApi.cs │ ├── NativeMethods.cs │ ├── NtDllApi.cs │ ├── ProcessApi.cs │ ├── PseudoConsoleApi.cs │ ├── SafeTokenHandle.cs │ ├── TokensApi.cs │ └── WindowApi.cs │ ├── ProcessHosts │ ├── AttachedConsoleHost.cs │ ├── IProcessHost.cs │ ├── NewWindowProcessHost.cs │ ├── PipedProcessHost.cs │ ├── TokenSwitchHost.cs │ └── VTProcessHost.cs │ ├── ProcessRenderers │ ├── AttachedConsoleRenderer.cs │ ├── IProcessRenderer.cs │ ├── PipedClientRenderer.cs │ ├── TokenSwitchRenderer.cs │ └── VTClientRenderer.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyAttributes.cs │ ├── PseudoConsole │ ├── PseudoConsole.cs │ ├── PseudoConsolePipe.cs │ └── PseudoConsoleProcess.cs │ ├── Rpc │ ├── Connection.cs │ ├── ConnectionKeepAliveThread.cs │ ├── IRpcClient.cs │ ├── IRpcServer.cs │ ├── NamedPipeClient.cs │ ├── NamedPipeNameFactory.cs │ ├── NamedPipeServer.cs │ └── NamedPipeUtils.cs │ ├── Tokens │ ├── IntegrityLevel.cs │ ├── Privilege.cs │ ├── PrivilegeManager.cs │ ├── TokenProvider.cs │ └── TokenSwitcher.cs │ ├── gsudo.csproj │ ├── gsudo.nuspec │ └── icon │ ├── gsudo.ico │ └── gsudo.svg └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1303: Do not pass literals as localized parameters 4 | dotnet_diagnostic.CA1303.severity = silent 5 | 6 | # CA2000: Dispose objects before losing scope 7 | dotnet_diagnostic.CA2000.severity = none 8 | 9 | # CA1031: Do not catch general exception types 10 | dotnet_diagnostic.CA1031.severity = none 11 | 12 | # IDE0008: Use explicit type 13 | csharp_style_var_for_built_in_types = false:silent 14 | 15 | # IDE0008: Use explicit type 16 | csharp_style_var_elsewhere = false:silent 17 | 18 | # IDE0008: Use explicit type 19 | csharp_style_var_when_type_is_apparent = false:silent 20 | 21 | # CA1062: Validate arguments of public methods 22 | dotnet_diagnostic.CA1062.severity = none 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/ @gerardog 2 | /build/ @gerardog -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gerardog 4 | patreon: # gerardog 5 | ko_fi: # Replace with a single Ko-fi username 6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 8 | liberapay: # Replace with a single Liberapay username 9 | issuehunt: # Replace with a single IssueHunt username 10 | otechie: # Replace with a single Otechie username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | custom: ['https://www.paypal.com/donate/?business=EGM43RDQ4VRWJ&no_recurring=0&item_name=%0APlease+help+me+dedicate+more+time+to+gsudo%21%0AIf+gsudo+has+improved+your+productivity%2C+please+contribute%21¤cy_code=USD'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug/Issue report 3 | about: Report a problem. 4 | title: 'Issue: ' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 13 | 14 | 15 | ## Issue Description 16 | 17 | 18 | ## Steps to Reproduce 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 24 | ## Screenshots 25 | 26 | 27 | 28 | ## Context: 29 | - Windows version: 30 | 32 | - gsudo version: 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: ❓ Question 4 | url: https://github.com/gerardog/gsudo/discussions/new 5 | about: Have a question on something? Start a new discussion thread. 6 | - name: 🔎 Search previous issues 7 | url: https://github.com/gerardog/gsudo/issues 8 | about: Please check if your issue has already been reported. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Idea/Feature Request 3 | about: Tell us about a feature or improvement that you would like to see implemented. 4 | title: 'Feature Request: ' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | 11 | 12 | 13 | ## Proposed technical details 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | branches: 11 | - '**' 12 | workflow_call: 13 | outputs: 14 | version: 15 | description: "Version" 16 | value: ${{ jobs.build.outputs.version }} 17 | version_MajorMinorPatch: 18 | description: "Version (without prerelease tag)" 19 | value: ${{ jobs.build.outputs.version_MajorMinorPatch }} 20 | is_prerelease: 21 | description: "Version prerelease ($true or $false)" 22 | value: ${{ jobs.build.outputs.is_prerelease }} 23 | 24 | jobs: 25 | test: 26 | name: Test 27 | runs-on: windows-latest 28 | permissions: 29 | id-token: write 30 | contents: read 31 | checks: write 32 | steps: 33 | - uses: actions/setup-dotnet@v4 34 | with: 35 | dotnet-version: '9.0.x' 36 | - uses: actions/checkout@v2 37 | with: 38 | fetch-depth: 0 39 | - name: Run Tests 40 | id: tests 41 | run: ./build/02-test.ps1 42 | - name: Test Report DotNet 43 | uses: dorny/test-reporter@v2.1.0 44 | if: success() || failure() 45 | with: 46 | name: TestsResults (dotnet) 47 | path: "**/TestResults*.trx" 48 | reporter: dotnet-trx 49 | fail-on-error: true 50 | - name: Test Report PowerShell v5 51 | uses: dorny/test-reporter@v2.1.0 52 | if: success() || failure() 53 | with: 54 | name: TestsResults (PowerShell v5) 55 | path: ./testResults_PS5.xml 56 | reporter: java-junit 57 | fail-on-error: true 58 | - name: Test Report PowerShell v7 59 | uses: dorny/test-reporter@v2.1.0 60 | if: success() || failure() 61 | with: 62 | name: TestsResults (PowerShell v7) 63 | path: ./testResults_PS7.xml 64 | reporter: java-junit 65 | fail-on-error: true 66 | build: 67 | name: Build 68 | runs-on: windows-latest 69 | steps: 70 | - uses: actions/setup-dotnet@v4 71 | with: 72 | dotnet-version: '9.0.x' 73 | - uses: actions/checkout@v2 74 | with: 75 | fetch-depth: 0 76 | - name: Install dependencies 77 | run: choco install GitVersion.Portable --version 5.6.11 --confirm --no-progress; choco install il-repack --confirm --no-progress 78 | - name: Update project version 79 | run: gitversion /l console /output buildserver /updateAssemblyInfo /verbosity minimal 80 | - name: Get project version 81 | id: getversion 82 | run: | 83 | echo "version=$(gitversion /showvariable LegacySemVer)" >> "$env:GITHUB_OUTPUT" 84 | echo "version_MajorMinorPatch=$(gitversion /showvariable MajorMinorPatch)" >> "$env:GITHUB_OUTPUT" 85 | if ("$(gitversion /showvariable PreReleaseTag)" -eq "") { 86 | echo "is_prerelease=0" >> "$env:GITHUB_OUTPUT" 87 | } else { 88 | echo "is_prerelease=1" >> "$env:GITHUB_OUTPUT" 89 | } 90 | get-content "$env:GITHUB_OUTPUT" 91 | - name: Build 92 | run: ./build/01-build.ps1 93 | - name: Upload build artifacts 94 | uses: actions/upload-artifact@v4 95 | with: 96 | name: Binaries 97 | path: ./artifacts 98 | outputs: 99 | version: ${{ steps.getversion.outputs.version }} 100 | version_MajorMinorPatch: ${{ steps.getversion.outputs.version_MajorMinorPatch }} 101 | is_prerelease: ${{ steps.getversion.outputs.is_prerelease }} 102 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs Build & Deploy 2 | 3 | on: 4 | # push: 5 | # branches: 6 | # - master 7 | # - docs 8 | # paths: 9 | # - 'docs/**' 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | workflow_call: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow one concurrent deployment 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - uses: actions/setup-node@v3 35 | with: 36 | node-version: 16.x 37 | cache: yarn 38 | cache-dependency-path: '**/yarn.lock' 39 | - name: Install dependencies 40 | run: yarn install --frozen-lockfile 41 | - name: Build website 42 | run: yarn build 43 | - name: Setup Pages 44 | uses: actions/configure-pages@v2 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v3 47 | with: 48 | # Upload entire repository 49 | path: './docs/build' 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v1 53 | -------------------------------------------------------------------------------- /.github/workflows/winget-solo.yml: -------------------------------------------------------------------------------- 1 | name: Winget manual dispatch 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publishToWinGet: 8 | name: Push to Winget 9 | if: github.repository == 'gerardog/gsudo' 10 | runs-on: windows-latest 11 | permissions: 12 | contents: write 13 | environment: 14 | name: release-chocolatey 15 | steps: 16 | - name: WinGet Releaser 17 | uses: vedantmgoyal9/winget-releaser@main 18 | with: 19 | token: ${{ secrets.WINGET_RELEASER_TOKEN }} 20 | identifier: gerardog.gsudo 21 | installers-regex: '\.msi$' # Only .msi files 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gerardo Grignoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.0.x | :white_check_mark: | 8 | | < 1.0 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | gsudo is a bridge between the elevated and unelevated world. A tradeof between convenience and security. In a worst case scenario, it should be equivalent to disabling UAC. It should never be possible to gain privileges if gsudo is installed but not running, or exploitable from network. 13 | 14 | Any vulnerability where gsudo is worse than disabling UAC altogether is a high priority vulnerability. (i.e. exploitation via network) 15 | 16 | We consider the security of gsudo a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. 17 | 18 | If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible. We would like to ask you to help us better protect gsudo users. 19 | 20 | Please do the following: 21 | 22 | - E-mail your findings to gerardog at gmail dot com and ping gerardo_gr on twitter. 23 | - Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data, 24 | - Do not reveal the problem to others until it has been resolved, 25 | - Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties, and 26 | - Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Usually, the IP address or the URL of the affected system and a description of the vulnerability will be sufficient, but complex vulnerabilities may require further explanation. 27 | 28 | What we promise: 29 | 30 | - We will respond to your report as quickly as possible, typically between 5 business days, with our evaluation of the report and an expected resolution date, 31 | - We will not take any legal action against you in regard to the report, 32 | - We will handle your report with strict confidentiality, and not pass on your personal details to third parties without your permission, 33 | - We will keep you informed of the progress towards resolving the problem. 34 | 35 | We strive to resolve all problems as quickly as possible, and we would like to play an active role in the ultimate publication on the problem after it is resolved. 36 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | gsudo.nuspec 2 | Chocolatey/gsudo/tools/* 3 | -------------------------------------------------------------------------------- /build/00-prerequisites.ps1: -------------------------------------------------------------------------------- 1 | choco install il-repack 2 | choco install GitVersion.Portable 3 | choco install wixtoolset 4 | choco install hub 5 | choco install dotnet-9.0-sdk 6 | choco install NuGet.CommandLine -------------------------------------------------------------------------------- /build/01-build.ps1: -------------------------------------------------------------------------------- 1 | pushd $PSScriptRoot\.. 2 | 3 | if ($env:version) { 4 | "- Getting version from env:version" 5 | $version = $env:version 6 | $version_MajorMinorPatch=$env:version_MajorMinorPatch 7 | } else { 8 | "- Getting version using GitVersion" 9 | $env:version = $version = $(gitversion /showvariable LegacySemVer) 10 | $env:version_MajorMinorPatch = $version_MajorMinorPatch=$(gitversion /showvariable MajorMinorPatch) 11 | } 12 | "- Using version number v$version / v$version_MajorMinorPatch" 13 | 14 | "-- Cleaning bin & obj folders" 15 | Get-Item ".\src\gsudo\bin\", ".\src\gsudo\obj\" -ErrorAction Ignore | Remove-Item -Recurse -Force 16 | "-- Building net9.0 win-arm64" 17 | dotnet publish .\src\gsudo\gsudo.csproj -c Release -o .\artifacts\arm64 -f net9.0 -r win-arm64 || $(exit $LASTEXITCODE) 18 | "-- Building net9.0 win-x64" 19 | dotnet publish .\src\gsudo\gsudo.csproj -c Release -o .\artifacts\x64 -f net9.0 -r win-x64 || $(exit $LASTEXITCODE) 20 | "-- Building net9.0 win-x86" 21 | dotnet publish .\src\gsudo\gsudo.csproj -c Release -o .\artifacts\x86 -f net9.0 -r win-x86 || $(exit $LASTEXITCODE) 22 | "-- Building net4.6 AnyCpu" 23 | dotnet publish .\src\gsudo\gsudo.csproj -c Release -o .\artifacts\net46-AnyCpu\unmerged -f net46 -p:Platform=AnyCpu -p:WarningLevel=0 || $(exit $LASTEXITCODE) 24 | 25 | "-- Repacking net4.6 AnyCpu into a single EXE" 26 | 27 | ilrepack .\artifacts\net46-AnyCpu\unmerged\gsudo.exe .\artifacts\net46-AnyCpu\unmerged\*.dll /out:.\artifacts\net46-AnyCpu\gsudo.exe /target:exe /targetplatform:v4 /ndebug /wildcards || $(exit $LASTEXITCODE) 28 | 29 | if ($?) { 30 | rm artifacts\net46-AnyCpu\unmerged -Recurse 31 | echo "artifacts\net46-AnyCpu\unmerged -> ilmerge -> artifacts\net46-AnyCpu\" 32 | } 33 | 34 | cp .\src\gsudo.Wrappers\* .\artifacts\x86 35 | cp .\src\gsudo.Wrappers\* .\artifacts\x64 36 | cp .\src\gsudo.Wrappers\* .\artifacts\arm64 37 | cp .\src\gsudo.Wrappers\* .\artifacts\net46-AnyCpu 38 | 39 | # Set Module version number. 40 | Get-ChildItem .\artifacts\ -Filter gsudoModule.psd1 -Recurse | % { (Get-Content $_) -replace """0.1""", """$version_MajorMinorPatch""" | Set-Content $_.FullName } 41 | 42 | popd -------------------------------------------------------------------------------- /build/02-test.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsAdmin { 2 | return (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 3 | } 4 | 5 | if (! (Test-IsAdmin)) { 6 | throw "Must be admin to run tests" 7 | } 8 | 9 | $failure=$false 10 | 11 | pushd $PSScriptRoot\.. 12 | 13 | dotnet build .\src\gsudo.sln || $(exit $LASTEXITCODE) 14 | 15 | $originalPath = $env:path 16 | 17 | $env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net9.0\).FullName+";" + [String]::Join(";", (($ENV:Path).Split(";") -notlike "*gsudo*" | % {$_ -replace "\\$" })) 18 | 19 | gsudo -k 20 | gsudo --debug cache on -p 0 -d 1 21 | $env:nokill=1 22 | gsudo -d --debug --integrity medium dotnet test .\src\gsudo.sln --no-build --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 23 | 24 | if (! $?) { $failure = $true } 25 | if ($failure) { exit 1 } # fail fast 26 | 27 | $script = { 28 | $ProgressPreference = "SilentlyContinue"; 29 | if ((Get-InstalledModule Pester -ErrorAction SilentlyContinue).Version -lt "5.0.0") { Install-Module Pester -Force -SkipPublisherCheck } 30 | Import-Module Pester 31 | 32 | $configuration = New-PesterConfiguration; 33 | $configuration.Run.Path = "src" 34 | $configuration.TestResult.Enabled = $true 35 | $configuration.TestResult.OutputPath = "TestResults_PS$($PSVersionTable.PSVersion.Major).xml" 36 | $configuration.TestResult.OutputFormat = "JUnitXml" 37 | # $configuration.Should.ErrorAction = 'Continue' 38 | # $configuration.CodeCoverage.Enabled = $true 39 | 40 | Invoke-Pester -Configuration $configuration 41 | } 42 | 43 | gsudo --debug cache on -p 0 -d 1 44 | Write-Verbose -verbose "Running PowerShell Tests on Windows PowerShell (v5.x)" 45 | gsudo --integrity medium powershell -noprofile $script -outputformat text 46 | if (! $?) { $failure = $true } 47 | 48 | gsudo cache on -p 0 -d 1 49 | Write-Verbose -verbose "Running PowerShell Tests on Pwsh Core (v7.x)" 50 | gsudo --integrity medium pwsh -noprofile $script 51 | if (! $?) { $failure = $true } 52 | 53 | gsudo.exe -k 54 | 55 | if ($failure) { exit 1 } 56 | -------------------------------------------------------------------------------- /build/03-sign.ps1: -------------------------------------------------------------------------------- 1 | pushd $PSScriptRoot\.. 2 | 3 | # Find SignTool.exe 4 | if ($env:SignToolExe) { 5 | # From env var. 6 | } elseif (get-command signtool.exe -ErrorAction Ignore) { 7 | # From path. 8 | $env:SignToolExe = (gcm signtool.exe).Path 9 | } elseif ($i = get-item "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" -ErrorAction Ignore) { 10 | $env:SignToolExe = $i.Fullname 11 | } elseif ($i = get-item "C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe" -ErrorAction Ignore) { 12 | $env:SignToolExe = $i.Fullname 13 | } else { 14 | Write-Output "SignTool Locations:" 15 | (Get-ChildItem -Path ${env:ProgramFiles(x86)} -Filter signtool.exe -Recurse -ErrorAction SilentlyContinue -Force).FullName 16 | popd 17 | throw "Unable to find SignTool.exe. Set env:SignToolExe to it's location." 18 | } 19 | 20 | if (!$env:cert_path) { throw 'Missing $env:cert_path variable'} 21 | if (!$env:cert_key) { throw 'Missing $env:cert_key variable'} 22 | 23 | if (!(gi "$env:cert_path")) { throw 'Missing $env:cert_path file'} 24 | 25 | "Using Signtool.exe from: $env:SignToolExe" 26 | "Using Certificate from: $env:cert_path" 27 | 28 | $files = @( 29 | "artifacts\x64\*.exe", 30 | "artifacts\x64\*.p*1" 31 | "artifacts\x86\*.exe", 32 | "artifacts\x86\*.p*1", 33 | "artifacts\net46-AnyCpu\*.exe", 34 | "artifacts\net46-AnyCpu\*.p*1", 35 | "artifacts\arm64\*.exe", 36 | "artifacts\arm64\*.p*1" 37 | ) -join " " 38 | 39 | # Accept $args override. 40 | if ($args) 41 | { 42 | $files = $args -join " " 43 | } 44 | 45 | $cmd = "& ""$env:SignToolExe"" sign /f ""$env:cert_path"" /p $env:cert_key /fd SHA256 /t http://timestamp.digicert.com $files" 46 | 47 | echo "`nInvoking SignTool.exe:`n" 48 | iex $cmd 49 | 50 | if (! $?) { 51 | popd 52 | exit 1 53 | } 54 | 55 | popd -------------------------------------------------------------------------------- /build/04-build-installer.ps1: -------------------------------------------------------------------------------- 1 | pushd $PSScriptRoot\.. 2 | function _exit {exit $args[0] } 3 | 4 | if ($env:version) { 5 | "- Getting version from env:version" 6 | $version = $env:version 7 | $version_MajorMinorPatch=$env:version_MajorMinorPatch 8 | } else { 9 | "- Getting version using GitVersion" 10 | $env:version = $version = $(gitversion /showvariable LegacySemVer) 11 | $env:version_MajorMinorPatch = $version_MajorMinorPatch=$(gitversion /showvariable MajorMinorPatch) 12 | } 13 | "- Using version number v$version / v$version_MajorMinorPatch" 14 | 15 | "- Packaging v$version" 16 | Get-ChildItem .\artifacts\ -File | Remove-Item # Remove files on artifacts root 17 | Get-ChildItem .\artifacts\ -Filter *.pdb -Recurse | Remove-Item # Remove *.pdb on subfolders 18 | 19 | Compress-Archive -Path ./artifacts/x86,./artifacts/x64,./artifacts/arm64,./artifacts/net46-AnyCpu -DestinationPath "artifacts/gsudo.portable.zip" -force -CompressionLevel Optimal 20 | (Get-FileHash artifacts\gsudo.portable.zip).hash > artifacts\gsudo.portable.zip.sha256 21 | 22 | "- Cleaning bin & obj folders" 23 | Get-Item ".\src\gsudo.Installer\bin\", ".\src\gsudo.Installer\obj\" -ErrorAction Ignore | Remove-Item -Recurse -Force 24 | 25 | (gc src\gsudo.Installer\Constants.Template.wxi) -replace '#VERSION#', "$version" | Out-File -encoding UTF8 src\gsudo.Installer\Constants.wxi 26 | 27 | "- Building Installer arm64" 28 | dotnet build .\src\gsudo.Installer\gsudomsi.wixproj -c Release -o .\artifacts -p:Platform=arm64 -v minimal -p:WarningLevel=0 || $(popd && _exit 1) 29 | "- Building Installer x64" 30 | dotnet build .\src\gsudo.Installer\gsudomsi.wixproj -c Release -o .\artifacts -p:Platform=x64 -v minimal -p:WarningLevel=0 || $(popd && _exit 1) 31 | "- Building Installer x86" 32 | dotnet build .\src\gsudo.Installer\gsudomsi.wixproj -c Release -o .\artifacts -p:Platform=x86 -v minimal -p:WarningLevel=0 || $(popd && _exit 1) 33 | 34 | Remove-Item .\artifacts\*.wixpdb 35 | 36 | "- Code Sign Installer" 37 | 38 | & $PSScriptRoot\03-sign.ps1 artifacts\*.msi || $(popd && _exit 1) 39 | Get-Item .\artifacts\*.msi | % {(Get-FileHash $_).Hash > "$_.sha256"} 40 | 41 | popd -------------------------------------------------------------------------------- /build/05-release-Chocolatey.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsAdmin { 2 | return (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 3 | } 4 | 5 | if (! (Test-IsAdmin)) { 6 | throw "Must be admin to properly test generated package" 7 | } 8 | 9 | pushd $PSScriptRoot\.. 10 | 11 | Get-Item .\artifacts\x64\gsudo.exe > $null || $(throw "Missing binaries/artifacts") 12 | 13 | if ($env:version) { 14 | "- Getting version from env:version" 15 | $version = $env:version 16 | $version_MajorMinorPatch=$env:version_MajorMinorPatch 17 | } else { 18 | "- Getting version using GitVersion" 19 | $version = $(gitversion /showvariable LegacySemVer) 20 | $version_MajorMinorPatch=$(gitversion /showvariable MajorMinorPatch) 21 | } 22 | "- Using version number v$version / v$version_MajorMinorPatch" 23 | 24 | "- Cleaning Choco template folder" 25 | git clean .\Build\Chocolatey\gsudo -xf 26 | 27 | "- Adding Artifacts" 28 | cp artifacts\x?? .\Build\Chocolatey\gsudo\tools -Recurse -Force -Exclude *.pdb 29 | Get-ChildItem .\build\Chocolatey\gsudo\tools\ -Recurse -Filter *.exe | % { ni "$($_.FullName).ignore" } > $null 30 | 31 | # Generate gsudo.nuspec 32 | (Get-Content Build\Chocolatey\gsudo.nuspec.template) -replace '#VERSION#', "$version" | Out-File -encoding UTF8 .\Build\Chocolatey\gsudo\gsudo.nuspec 33 | # Generate Tools\VERIFICATION.txt 34 | Get-Content .\Build\Chocolatey\verification.txt.template | Out-File -encoding UTF8 .\Build\Chocolatey\gsudo\Tools\VERIFICATION.txt 35 | 36 | "- Calculating Hashes " 37 | 38 | @" 39 | --- 40 | Version Hashes for v$version 41 | 42 | "@ >> .\Build\Chocolatey\gsudo\Tools\VERIFICATION.txt 43 | Get-FileHash .\Build\Chocolatey\gsudo\Tools\*\*.* | Out-String -Width 200 | %{$_.Replace("$((gi Build\Chocolatey\gsudo).FullName)\", "",'OrdinalIgnoreCase')} >> .\Build\Chocolatey\gsudo\Tools\VERIFICATION.txt 44 | Get-childitem *.bak -Recurse | Remove-Item 45 | 46 | "- Packing v$version to chocolatey" 47 | mkdir Artifacts\Chocolatey -Force > $null 48 | & choco pack .\Build\Chocolatey\gsudo\gsudo.nuspec -outdir="$((get-item Artifacts\Chocolatey).FullName)" || $(throw "Choco pack failed.") 49 | 50 | "- Testing package" 51 | if (choco list -lo | Where-object { $_.StartsWith("gsudo") }) { 52 | choco upgrade gsudo --failonstderr -s Artifacts\Chocolatey -f -pre --confirm || $(throw "Choco upgrade failed.") 53 | } else { 54 | choco install gsudo --failonstderr -s Artifacts\Chocolatey -f -pre --confirm || $(throw "Choco install failed.") 55 | } 56 | 57 | if($(choco apikey).Count -lt 2) { throw "Missing Chocolatey ApiKey. Use: choco apikey -k -s https://push.chocolatey.org/" } 58 | 59 | "`n- Uploading v$version to chocolatey" 60 | choco push artifacts\Chocolatey\gsudo.$($version).nupkg --source=https://push.chocolatey.org/ || $(throw "Choco push failed.") 61 | 62 | "- Success" 63 | popd -------------------------------------------------------------------------------- /build/06-release-Nuget.ps1: -------------------------------------------------------------------------------- 1 | pushd $PSScriptRoot\.. 2 | 3 | Get-Item .\artifacts\x64\gsudo.exe > $null || $(throw "Missing binaries/artifacts") 4 | 5 | if ($env:version) { 6 | "- Getting version from env:version" 7 | $version = $env:version 8 | $version_MajorMinorPatch=$env:version_MajorMinorPatch 9 | } else { 10 | "- Getting version using GitVersion" 11 | $version = $(gitversion /showvariable LegacySemVer) 12 | $version_MajorMinorPatch=$(gitversion /showvariable MajorMinorPatch) 13 | } 14 | "- Using version number v$version / v$version_MajorMinorPatch" 15 | 16 | "- Cleaning Nuget template folder" 17 | git clean .\Build\Nuget\gsudo -xf 18 | 19 | "- Packing v$version to nuget" 20 | dotnet build .\Build\Nuget\gsudo.csproj /p:Version=$version -o artifacts\Nuget || $(throw "Nuget pack failed.") 21 | 22 | "`n- Uploading v$version to Nuget" 23 | gi "artifacts\nuget\gsudo.$($version).nupkg" || $(throw "Nuget push failed.") 24 | nuget push artifacts\nuget\gsudo.$($version).nupkg -Source https://api.nuget.org/v3/index.json || $(throw "Nuget push failed.") 25 | 26 | 27 | "- Success" 28 | popd -------------------------------------------------------------------------------- /build/Chocolatey/VERIFICATION.txt.template: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | 1. The embedded files are also available as a release in GitHub. 6 | 7 | Please go to releases page 8 | https://github.com/gerardog/gsudo/releases 9 | 10 | Download same version as this choco package (example for v0.4) 11 | https://github.com/gerardog/gsudo/releases/tag/v0.4 12 | 13 | unzip and get hashes 14 | powershell -command Get-FileHash * 15 | 16 | 2. Get hashes from this choco package. Run: 17 | 18 | powershell -command Get-FileHash bin\* 19 | 20 | 3. The checksums should match the following: 21 | -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/Bin/gsudo.exe.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/build/Chocolatey/gsudo/Bin/gsudo.exe.ignore -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/Bin/sudo.exe.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/build/Chocolatey/gsudo/Bin/sudo.exe.ignore -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/tools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | From: https://opensource.org/licenses/MIT 2 | 3 | LICENSE 4 | 5 | Copyright 2019 Gerardo Grignoli 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/tools/Uninstall-ChocolateyPath.psm1: -------------------------------------------------------------------------------- 1 | function Uninstall-ChocolateyPath { 2 | <# 3 | .SYNOPSIS 4 | **NOTE:** Administrative Access Required when `-PathType 'Machine'.` 5 | 6 | This puts a directory to the PATH environment variable. 7 | 8 | .DESCRIPTION 9 | Looks at both PATH environment variables to ensure a path variable 10 | does not show up on the right PATH. 11 | 12 | .NOTES 13 | This command will assert UAC/Admin privileges on the machine if 14 | `-PathType 'Machine'`. 15 | 16 | This is used when the application/tool is not being linked by Chocolatey 17 | (not in the lib folder). 18 | 19 | .INPUTS 20 | None 21 | 22 | .OUTPUTS 23 | None 24 | 25 | .PARAMETER PathToUninstall 26 | The full path to a location to remove / ensure is not in the PATH. 27 | 28 | .PARAMETER PathType 29 | Which PATH to remove from it. If specifying `Machine`, this requires admin 30 | privileges to run correctly. 31 | 32 | .PARAMETER IgnoredArguments 33 | Allows splatting with arguments that do not apply. Do not use directly. 34 | 35 | .EXAMPLE 36 | Uninstall-ChocolateyPath -PathToUninstall "$($env:SystemDrive)\tools\gittfs" 37 | 38 | .EXAMPLE 39 | Uninstall-ChocolateyPath "$($env:SystemDrive)\Program Files\MySQL\MySQL Server 5.5\bin" -PathType 'Machine' 40 | 41 | .LINK 42 | Install-ChocolateyPath 43 | 44 | .LINK 45 | Install-ChocolateyEnvironmentVariable 46 | 47 | .LINK 48 | Uninstall-ChocolateyEnvironmentVariable 49 | 50 | .LINK 51 | Get-EnvironmentVariable 52 | 53 | .LINK 54 | Set-EnvironmentVariable 55 | 56 | .LINK 57 | Get-ToolsLocation 58 | #> 59 | param( 60 | [parameter(Mandatory=$true, Position=0)][string] $pathToUninstall, 61 | [parameter(Mandatory=$false, Position=1)][System.EnvironmentVariableTarget] $pathType = [System.EnvironmentVariableTarget]::User, 62 | [parameter(ValueFromRemainingArguments = $true)][Object[]] $ignoredArguments 63 | ) 64 | 65 | Write-FunctionCallLogMessage -Invocation $MyInvocation -Parameters $PSBoundParameters 66 | ## Called from chocolateysetup.psm1 - wrap any Write-Host in try/catch 67 | 68 | $originalPathToUninstall = $pathToUninstall 69 | 70 | $statementTerminator = ";" 71 | 72 | # if the last digit is ;, then we are removing it 73 | if ($pathToUninstall.EndsWith($statementTerminator)) { 74 | $pathToUninstall = $pathToUninstall.Substring(0, $pathToUninstall.LastIndexOf($statementTerminator)) 75 | } 76 | 77 | #get the PATH variable 78 | Update-SessionEnvironment 79 | $envPath = $env:PATH 80 | if ($envPath.ToLower().Contains($pathToUninstall.ToLower())) 81 | { 82 | try { 83 | Write-Host "PATH environment variable has $pathToUninstall in it. Removing..." 84 | } catch { 85 | Write-Verbose "PATH environment variable has $pathToUninstall in it. Removing..." 86 | } 87 | 88 | $actualPath = Get-EnvironmentVariable -Name 'Path' -Scope $pathType -PreserveVariables 89 | 90 | $actualPath = $actualPath.Replace($pathToUninstall, "") 91 | 92 | while ($actualPath.Contains($statementTerminator + $statementTerminator)) { 93 | $actualPath = $actualPath.Replace($statementTerminator + $statementTerminator, $statementTerminator) 94 | } 95 | 96 | if ($pathType -eq [System.EnvironmentVariableTarget]::Machine) { 97 | if (Test-ProcessAdminRights) { 98 | Set-EnvironmentVariable -Name 'Path' -Value $actualPath -Scope $pathType 99 | } else { 100 | $psArgs = "Uninstall-ChocolateyPath -pathToUninstall `'$originalPathToUninstall`' -pathType `'$pathType`'" 101 | Start-ChocolateyProcessAsAdmin "$psArgs" 102 | } 103 | } else { 104 | Set-EnvironmentVariable -Name 'Path' -Value $actualPath -Scope $pathType 105 | } 106 | 107 | #removing it from the local path as well 108 | $envPSPath = $env:PATH 109 | $envPSPath = $envPSPath.Replace($pathToUninstall, "") 110 | while($envPSPath.Contains($statementTerminator + $statementTerminator)) { 111 | $envPSPath = $envPSPath.Replace($statementTerminator + $statementTerminator, $statementTerminator) 112 | } 113 | $env:Path = $envPSPath 114 | } 115 | } -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/tools/chocolateybeforemodify.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Ignore' 2 | 3 | if (Get-Process gsudo 2> $null) { 4 | if (Get-Command gsudo.exe 2> $null) { 5 | # Stop any gsudo open cache sessions, if any. 6 | gsudo.exe -k 2> $null 7 | Start-Sleep -Milliseconds 500 8 | } 9 | } 10 | 11 | $ToolsLocation = Get-ToolsLocation 12 | if ([System.Environment]::CurrentDirectory -like "$ToolsLocation*") { 13 | Write-Output -Verbose "Changing directory to $ToolsLocation to ensure successfull install/upgrade." 14 | Set-Location $ToolsLocation 15 | } 16 | 17 | $ErrorActionPreference = 'Continue' 18 | -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/tools/chocolateyinstall.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsSymLink([string]$path) { 2 | $file = Get-Item $path -Force -ea SilentlyContinue 3 | return [bool]($file.Attributes -band [IO.FileAttributes]::ReparsePoint) 4 | } 5 | 6 | Import-Module (Join-Path (Split-Path -parent $MyInvocation.MyCommand.Definition) "Uninstall-ChocolateyPath.psm1") 7 | 8 | $ErrorActionPreference = 'Continue' 9 | $ToolsLocation = Get-ToolsLocation 10 | 11 | if ([Environment]::Is64BitOperatingSystem -eq $true) { 12 | $bin = "$env:ChocolateyInstall\lib\gsudo\tools\x64" 13 | } else { 14 | $bin = "$env:ChocolateyInstall\lib\gsudo\tools\x86" 15 | } 16 | 17 | if ([System.Environment]::CurrentDirectory -like "$ToolsLocation*") { 18 | Write-Output -Verbose "Changing directory to $ToolsLocation to ensure successfull install/upgrade." 19 | Set-Location $ToolsLocation 20 | } 21 | 22 | $TargetDir = ("$ToolsLocation\gsudo\v" + ((Get-Item "$bin\gsudo.exe").VersionInfo.ProductVersion -split "\+" )[0]) 23 | $SymLinkDir = "$ToolsLocation\gsudo\Current" 24 | 25 | # Add to System Path 26 | mkdir $TargetDir -ErrorAction Ignore > $null 27 | copy "$bin\*" $TargetDir -Exclude *.ignore -Force 28 | Install-ChocolateyPath -PathToInstall $SymLinkDir -PathType 'Machine' 29 | 30 | cmd /c mklink "$TargetDir\sudo.exe" "$TargetDir\gsudo.exe" 2>$null 31 | 32 | # Copy gsudoModule to "$env:ProgramFiles\PowerShell\Modules\gsudoModule" 33 | $PSModulesTargetDir = "$env:ProgramFiles\WindowsPowerShell\Modules\gsudoModule" 34 | md $PSModulesTargetDir -ErrorAction SilentlyContinue 35 | copy "$bin\*.ps*" $PSModulesTargetDir -Exclude *.ignore -Force 36 | 37 | $OldCurrentDir = Get-Item $SymLinkDir -ErrorAction ignore 38 | if ($OldCurrentDir) 39 | { 40 | $OldCurrentDir.Delete() 41 | } 42 | 43 | cmd /c mklink /d "$SymLinkDir" "$TargetDir\" 44 | 45 | # gsudo powershell module banner. 46 | ""; 47 | 48 | if (Get-Module gsudoModule) { 49 | "Please restart all your PowerShell consoles to update PowerShell gsudo Module." 50 | } else { 51 | & { 52 | "PowerShell users: Add auto-complete to gsudo by adding the following line to your `$PROFILE" 53 | " Import-Module 'gsudoModule'" 54 | "Or run: " 55 | " Write-Output `"``nImport-Module 'gsudoModule'`" | Add-Content `$PROFILE" 56 | 57 | } 58 | } 59 | 60 | Write-Output "gsudo successfully installed. Please restart your consoles to use gsudo.`n" 61 | -------------------------------------------------------------------------------- /build/Chocolatey/gsudo/tools/chocolateyuninstall.ps1: -------------------------------------------------------------------------------- 1 | Import-Module (Join-Path (Split-Path -parent $MyInvocation.MyCommand.Definition) "Uninstall-ChocolateyPath.psm1") 2 | 3 | function MarkFileDelete { 4 | param( 5 | [parameter(Mandatory=$true)] 6 | [string] $path 7 | ) 8 | 9 | # the code below has been used from 10 | # https://blogs.technet.com/b/heyscriptingguy/archive/2013/10/19/weekend-scripter-use-powershell-and-pinvoke-to-remove-stubborn-files.aspx 11 | # with inspiration from 12 | # http://www.leeholmes.com/blog/2009/02/17/moving-and-deleting-really-locked-files-in-powershell/ 13 | # and error handling from 14 | # https://blogs.technet.com/b/heyscriptingguy/archive/2013/06/25/use-powershell-to-interact-with-the-windows-api-part-1.aspx 15 | 16 | Add-Type @' 17 | using System; 18 | using System.Text; 19 | using System.Runtime.InteropServices; 20 | 21 | public class Posh 22 | { 23 | public enum MoveFileFlags 24 | { 25 | MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004 26 | } 27 | 28 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 29 | static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); 30 | 31 | public static bool MarkFileDelete (string sourcefile) 32 | { 33 | return MoveFileEx(sourcefile, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT); 34 | } 35 | } 36 | '@ 37 | $deleteResult = [Posh]::MarkFileDelete($path) 38 | 39 | if ($deleteResult) { 40 | write-Warning "(Delete of $path failed: Will be deleted at next boot.)" 41 | } else { 42 | write-Warning "(Error marking $path for deletion at next boot.)" 43 | } 44 | } 45 | 46 | 47 | $installPath = "$(Get-ToolsLocation)\gsudo" 48 | Uninstall-ChocolateyPath "$installPath\Current" 'Machine' 49 | 50 | $ErrorActionPreference="Ignore" 51 | 52 | # Delete symlinks in Pwsh 5. 53 | Get-ChildItem $installPath -Recurse |? LinkType -eq 'SymbolicLink'|%{$_.Delete()} 54 | # Delete the rest. 55 | Remove-Item $installPath -Recurse -Force -ErrorAction Ignore 56 | Remove-Item $installPath -Recurse -Force -ErrorAction Ignore 57 | 58 | if (Test-Path $installPath) { 59 | # Files are in use so delete failed. 60 | # Rename used files and directories. 61 | 62 | Get-ChildItem $installPath -Recurse -Exclude "*.deleteMe" | Sort-Object -Descending {(++$script:i)} | % { Rename-Item -Path $_.FullName -NewName ($_.Name + ".deleteMe") ; } *> $NULL 63 | # Mark remaining for delete after restart. 64 | Get-ChildItem $installPath -Recurse | % { MarkFileDelete ( $_.FullName) } 65 | MarkFileDelete ( $installPath ); 66 | } 67 | 68 | $ErrorActionPreference = 'Continue' 69 | -------------------------------------------------------------------------------- /build/DemoGif/demo.ahk: -------------------------------------------------------------------------------- 1 | SetKeyDelay,100 2 | Send cls 3 | Send {enter} 4 | Sleep 3000 5 | Send scoop install gsudo{enter} 6 | Sleep 4000 7 | Send gsudo -v 8 | Sleep 300 9 | Send {enter} 10 | Sleep 500 11 | Send sudo -v 12 | Sleep 500 13 | Send {enter} 14 | Sleep 500 15 | SetKeyDelay,40 16 | SendRaw, ......... You can use "gsudo" or the "sudo" alias. 17 | Sleep 1000 18 | Send ^C 19 | SetKeyDelay,100 20 | Sleep 1000 21 | Send type MySecret.txt 22 | Send {enter} 23 | Sleep 1500 24 | SendRaw, sudo type MySecret.txt 25 | Send {enter} 26 | Sleep 4000 27 | SetKeyDelay,40 28 | SendRaw, ......... Note UAC popup appeared and the command ran elevated! 29 | SetKeyDelay,100 30 | Sleep 1000 31 | Send ^C 32 | Send sudo type MySecret.txt 33 | Send {enter} 34 | Sleep 1000 35 | SetKeyDelay,40 36 | SendRaw, ......... Note no UAC popup appeared on additional commands! 37 | SetKeyDelay,100 38 | Sleep 1200 39 | Send ^C 40 | Send sudo{enter} 41 | Sleep 1000 42 | SetKeyDelay,40 43 | SendRaw, ......... Elevated shell. Note: Tab key autocomplete works 44 | SetKeyDelay,100 45 | Sleep 1000 46 | Send ^C 47 | SendRaw, echo MyNewSecret1234 > my 48 | Sleep 200 49 | Send {tab} 50 | Sleep 500 51 | Send {tab} 52 | Sleep 500 53 | Send {enter} 54 | Send exit{enter} 55 | Send powershell{enter} 56 | SetKeyDelay,40 57 | SendRaw, #......... Powershell commands can also be elevated! 58 | Send {enter} 59 | Sleep 1000 60 | SetKeyDelay,100 61 | SendRaw, Get-FileHash MySecret.txt 62 | Send {enter} 63 | Sleep 1000 64 | aa: 65 | SendRaw, $hash = sudo '(Get-FileHash MySecret.txt).hash' ' 66 | Send {enter} 67 | Sleep 1000 68 | SetKeyDelay,150 69 | SendRaw, $hash 70 | Sleep 500 71 | Send {enter} 72 | Sleep 5000 73 | SetKeyDelay,50 74 | Send exit{enter} -------------------------------------------------------------------------------- /build/DemoGif/demo.bat: -------------------------------------------------------------------------------- 1 | md \test 2 | cd \test 3 | GSUDO del MySecret.txt 4 | GSUDO echo 1234!>MySecret.txt 5 | ICACLS MySecret.txt /inheritance:r /grant BUILTIN\Administrators:(F) 6 | tskill gsudo 7 | call scoop uninstall gsudo 8 | call scoop cache rm gsudo 9 | start /min autohotkey "%~dp0demo.ahk" 10 | :todo add pipe redirection. -------------------------------------------------------------------------------- /build/Nuget/gsudo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.0.0.0 5 | Gerardo Grignoli 6 | Gerardo Grignoli 7 | gsudo 8 | gsudo;sudo;runas 9 | gsudo is a sudo for Windows, allows to run commands with elevated permissions in the current console. 10 | netstandard2.0 11 | true 12 | false 13 | false 14 | true 15 | false 16 | true 17 | Gerardo Grignoli and GitHub Contributors 18 | MIT 19 | https://github.com/gerardog/gsudo 20 | https://github.com/gerardog/gsudo 21 | GIT 22 | 23 | 24 | 25 | 26 | runtimes\win-anycpu\native\gsudo.exe 27 | 28 | 29 | runtimes\win-x86\native\gsudo.exe 30 | 31 | 32 | runtimes\win-x64\native\gsudo.exe 33 | 34 | 35 | runtimes\win-arm64\native\gsudo.exe 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /build/Nuget/gsudo.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gsudo.exe 6 | PreserveNewest 7 | Included 8 | false 9 | false 10 | 11 | 12 | runtimes\win-x86\native\gsudo.exe 13 | PreserveNewest 14 | Included 15 | false 16 | false 17 | 18 | 19 | runtimes\win-x64\native\gsudo.exe 20 | PreserveNewest 21 | Included 22 | false 23 | false 24 | 25 | 26 | runtimes\win-arm64\native\gsudo.exe 27 | PreserveNewest 28 | Included 29 | false 30 | false 31 | 32 | 33 | -------------------------------------------------------------------------------- /build/Scoop/gsudo.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.4.1", 3 | "description": "A Sudo for Windows", 4 | "homepage": "https://github.com/gerardog/gsudo", 5 | "license": "MIT", 6 | "url": "https://github.com/gerardog/gsudo/releases/download/v0.4.1/gsudo.v0.4.1.zip", 7 | "hash": "5a7c2bdaa5e2c399c99bd396fea6e35a3267c62d901e7326959b17b1abaf4a3b", 8 | "bin": [ 9 | "gsudo.exe", 10 | [ 11 | "gsudo.exe", 12 | "sudo" 13 | ] 14 | ], 15 | "checkver": "github", 16 | "autoupdate": { 17 | "url": "https://github.com/gerardog/gsudo/releases/download/v$version/gsudo.v$version.zip" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/demo.gif -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This is the source code of gsudo documentation website. 4 | 5 | The documentation is available at: https://gerardog.github.io/gsudo/ 6 | 7 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 8 | 9 | ### Website Installation 10 | 11 | ``` 12 | $ yarn 13 | ``` 14 | 15 | ### Local Development 16 | 17 | ``` 18 | $ yarn start 19 | ``` 20 | 21 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 22 | 23 | ### Build 24 | 25 | ``` 26 | $ yarn build 27 | ``` 28 | 29 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 30 | 31 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | gerardog: 2 | name: Gerardo Grignoli 3 | # title: gsudo Creator 4 | url: https://github.com/gerardog 5 | image_url: https://avatars.githubusercontent.com/u/3901474?s=96&v=4 6 | -------------------------------------------------------------------------------- /docs/docs/credentials-cache.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Credentials Cache 4 | #hide_title: true 5 | --- 6 | 7 | ## Introduction 8 | 9 | The `Credentials Cache` is an optional feature that allows to elevate several times from a parent process with only one UAC pop-up. 10 | 11 | An active credentials cache session is just an elevated instance of gsudo that stays running and allows the invoker process to elevate again. No windows service or setup involved. 12 | 13 | It is convenient, but it's safe only if you are not already hosting a malicious process: No matter how secure gsudo itself is, a malicious process could [trick](https://en.wikipedia.org/wiki/DLL_injection#Approaches_on_Microsoft_Windows) the allowed process (Cmd/Powershell) and force a running `gsudo` cache instance to elevate silently. 14 | 15 | ## Cache Modes 16 | 17 | The cache mode can be set with **`gsudo config CacheMode auto|explicit|disabled`** 18 | 19 | - **Explicit: (default)** Every elevation shows a UAC popup, unless a cache session is started explicitly with `gsudo cache on`. 20 | 21 | ```powershell 22 | gsudo config CacheMode Explicit # This is optional since Explicit is the default. 23 | 24 | gsudo command1 # 1st UAC Popup shown 25 | gsudo command2 # 2nd UAC Popup shown 26 | gsudo cache on # 3rd UAC Popup shown, cache session started 27 | gsudo command3 # Elevation without popup 28 | gsudo command4 # Elevation without popup 29 | gsudo cache off # (or gsudo -k) Ends the cache session. Next elevation will show a UAC popup. 30 | ``` 31 | 32 | - **Auto:** Simil-unix-sudo. The first elevation shows a UAC Popup and starts a cache session automatically. 33 | 34 | ```powershell 35 | gsudo config CacheMode Auto # Only need to run once, this setting is persistent. 36 | 37 | gsudo command1 # 1st UAC Popup shown, cache session started 38 | gsudo command2 # Elevation without popup 39 | gsudo command3 # Elevation without popup 40 | gsudo cache off # (or gsudo -k) Ends the cache session. Next elevation will show a UAC popup. 41 | ``` 42 | 43 | - **Disabled:** Every elevation request shows a UAC popup. Attempts to start a Cache Session throws error. 44 | 45 | --- 46 | 47 | Use `gsudo cache on|off` to start/stop a cache session manually (i.e. allow/disallow elevation of the current process with no additional UAC popups). 48 | 49 | Use `gsudo -k` to terminate all cache sessions. (Use this before leaving your computer unattended to someone else.) 50 | 51 | The cache session ends automatically when the allowed process ends or if no elevations requests are received for 5 minutes (configurable via `gsudo config CacheDuration`). 52 | 53 | ## Usage 54 | 55 | - `gsudo cache [-h]` Shows cache help 56 | - `gsudo cache {on | off} [-p {pid}] [-d {time}]` Start/stop a gsudo cache session. 57 | 58 | - `-p | --pid {pid}` Specify which process can use the cache. (Use 0 for any, Default=`caller pid`) 59 | - `-d | --duration {hh:mm:ss}` Max time the cache can stay idle before closing. 60 | - Use '-1' to keep open until logoff (or until `cache off`, or `-k`). 61 | - The default is `CacheDuration` is 5 minutes. 62 | -------------------------------------------------------------------------------- /docs/docs/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide_title: true 3 | title: How to Install 4 | sidebar_position: 2 5 | --- 6 | import Tabs from '@theme/Tabs'; 7 | import TabItem from '@theme/TabItem'; 8 | 9 | ## How to Install gsudo 10 | 11 | 12 | 13 | 14 | If you use any of the following Package Managers: 15 | 16 | - Using [WinGet](https://github.com/microsoft/winget-cli/releases): Run `winget install gerardog.gsudo` 17 | - Using [Chocolatey](https://chocolatey.org/install): Run `choco install gsudo` 18 | - Using [Scoop](https://scoop.sh): Run `scoop install gsudo` 19 | 20 | Or: 21 | 22 | - Download and run the `MSI` file from the [latest release](https://github.com/gerardog/gsudo/releases/latest). 23 | - Or use the following script to achieve the same: 24 | ```powershell 25 | PowerShell -Command "Set-ExecutionPolicy RemoteSigned -scope Process; [Net.ServicePointManager]::SecurityProtocol = 'Tls12'; iwr -useb https://raw.githubusercontent.com/gerardog/gsudo/master/installgsudo.ps1 | iex" 26 | ``` 27 | - Manually: Download the `ZIP` file from the [latest release](https://github.com/gerardog/gsudo/releases/latest). Uncompress and add to `PATH`. 28 | 29 | 30 | 31 | 32 | 33 | - Download `gsudo.Setup.[platform].msi` from the [latest release](https://github.com/gerardog/gsudo/releases/latest), and run. 34 | 35 | 36 | 37 | 38 | 39 | 40 | - Enable TLS 1.2 using [Microsoft "Easy Fix"](https://support.microsoft.com/en-us/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392#bkmk_easy) 41 | - Download `gsudo.Setup.[platform].msi` from the [latest release](https://github.com/gerardog/gsudo/releases/latest), and run. 42 | - You probably want to update PowerShell up to 5.1 43 | 44 | 45 | 46 | 47 | 48 | --- 49 | 50 | :::caution 51 | Please restart your consoles after installing, to refresh the `PATH` environment variable. 52 | ::: 53 | 54 | 55 | :::info 56 | `gsudo` is just a portable console app. No Windows service is required or system change is done, except adding gsudo to the `PATH`. 57 | ::: 58 | 59 | 60 | ## Configure your Shell 61 | 62 | On the following shells you get a better experience if you follow some manual configuration: 63 | 64 | - [PowerShell](usage/powershell#powershell-profile-config) 65 | - [Bash for Windows (MinGW / MSYS2 / Git-Bash / Cygwin)](usage/bash-for-windows#bash-profile-config) 66 | -------------------------------------------------------------------------------- /docs/docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | hide_title: true 4 | title: Introduction 5 | --- 6 | ## Introduction 7 | 8 | **gsudo** is a `sudo` equivalent for Windows, with a similar user-experience as the original *nix sudo. 9 | 10 | It allows to run commands with elevated permissions, or to elevate the current shell, in the current console window or a new one. 11 | 12 | Just prepend `gsudo` (or the `sudo` alias) to your command and it will run elevated. One UAC popup will appear each time. You can see less popups if you enable [gsudo cache](credentials-cache). 13 | 14 | It is designed so it is easy to install, easy to use, and feels familiar with other popular tools. 15 | 16 | :::info 17 | `gsudo` allows you to easily cherry-pick which commands to elevate, and save time by not switching context between elevated and non-elevated windows. 18 | ::: 19 | 20 | ### Features 21 | 22 | - It is a proper `sudo for windows`: 23 | - Executes the desired command with elevated permissions (or as another user). 24 | - Elevated commands are shown in the current user-level console. No new window. (Unless you specify `-n`) 25 | - Full console support: Colors, full keyboard, auto-completion, etc. 26 | - Supports I/O redirection. 27 | - Handles Ctrl-C properly 28 | - Supports worldwide encodings & codepages 29 | - Uses the current shell to interpret the command to elevate: 30 | - `gsudo {command}` uses a new instance of the invoking shell to elevate the command. 31 | For example, in PowerShell `gsudo mkdir x` becames `pwsh -c "mkdir x"`, while in CMD it becames `cmd /c "mkdir x"`. 32 | - Supported Shells: 33 | - [CMD](usage) 34 | - [PowerShell](usage/powershell) 35 | - [WSL](usage/wsl) 36 | - [Bash for Windows (MSYS2 / MinGW / Git-Bash / Cygwin)](usage/bash-for-windows) 37 | - Yori 38 | - Take Command 39 | - NuShell 40 | - If no command is specified, it starts an elevated shell. 41 | - [Credentials cache](#credentials-cache): `gsudo` can elevate many times showing only one UAC pop-up if the user opts-in to enable the cache. 42 | - Supports being used on scripts: 43 | - Returns the command exit code (`%errorlevel%`). If `gsudo` fails to elevate, the exit code will be 999. 44 | - If `gsudo` is invoked from an already elevated console, it will run the command as-is (won't throw error). So, don't worry if you run `gsudo` or a script that uses `gsudo` when already elevated. (No elevation is required, no UAC popup) 45 | 46 | - Use `gsudo !!` to elevate the last ran command. Works on CMD, Git-Bash, MinGW, MSYS2, Cygwin (and PowerShell with [gsudo module](usage/powershell#powershell-profile-config) only) 47 | 48 | Read [How to Use](usage) for your favorite shell to see additional features. 49 | 50 | ### Demo 51 | 52 | [![Demo](../../demo.gif)](../../demo.gif) -------------------------------------------------------------------------------- /docs/docs/tips/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tips & Hacks", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/tips/elevation-in-new-window.md: -------------------------------------------------------------------------------- 1 | --- 2 | #sidebar_position: 6 3 | title: Force elevation in new window 4 | #hide_title: true 5 | --- 6 | 7 | As mentioned in [Security Considerations](../security.md#what-are-the-risks-of-running-gsudo), elevation in the same console exposes a mild risk. You can avoid it altogether by elevating in a new console window, for example by adding the `--new` or `-n` switch. 8 | 9 | In gsudo v2, a new setting was introduced to force elevations in a new window. To enable it simply run: 10 | 11 | ``` powershell 12 | gsudo config NewWindow.Force true 13 | ``` 14 | 15 | The problem with this approach is that some elevations complete too quickly and the new window may close abruptly. So to leave time to verify the window result combine it with one of the following new switches: 16 | 17 | - `--KeepWindow`: After running a command in a new console, ask for keypress before closing the console/window. 18 | - `--KeepShell`: After running a command, keep the elevated shell open. 19 | 20 | You set one of those switches permanently by using the following setting: 21 | 22 | ``` powershell 23 | gsudo config NewWindow.CloseBehaviour [ KeepShellOpen | PressKeyToClose | OsDefault ] 24 | ``` 25 | 26 | Valid values are: 27 | - **KeepShellOpen**: Forces `--keepShell`. Keep the elevated shell open after running the command. 28 | ![KeepShellOpen](../static/img/../../../static/img/NewWindow.CloseBehaviour.KeepShellOpen.png) 29 | 30 | - **PressKeyToClose**: Forces `--keepWindow`. Asks for a keypress before closing the window. 31 | ![PressKeyToClose](../static/img/../../../static/img/NewWindow.CloseBehaviour.PressKeyToClose.png) 32 | 33 | - **OsDefault**: (default) Launch the command and let the operating system close it or keep it according to the system defaults (typically the window will close). 34 | -------------------------------------------------------------------------------- /docs/docs/tips/mixed-elevation-in-windows-terminal.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | #id: usage 4 | title: Windows Terminal 5 | hide_title: true 6 | --- 7 | 8 | ## Mixed Elevation of tabs 9 | 10 | You can have an elevated tab inside `Windows Terminal` using gsudo. Just add a new profile (or edit an existing one), edit your command and prepend `gsudo `. Or just put `gsudo cmd` or `gsudo pwsh` as your command. 11 | 12 | However, I personally prefer to not do this and use the following alternative. 13 | 14 | ## Elevate on demand 15 | 16 | Use gsudo's ability to elevate the current shell in the current window. Just run `gsudo` and it will invoke caller shell (CMD, PowerShell, etc) elevated. Or even better, prepend `gsudo` to elevate specific commands. 17 | -------------------------------------------------------------------------------- /docs/docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | hide_title: true 4 | title: Troubleshooting 5 | --- 6 | 7 | ## Troubleshooting 8 | 9 | - After installation / upgrade. Please close your consoles and open new ones. 10 | - Same with error `Unauthorized. (Different gsudo.exe?)`. 11 | 12 | - Be sure you have [configured your shell](install#configure-your-shell) 13 | 14 | - Use `gsudo --debug {command}` to see internal debug info. 15 | 16 | - Search [gsudo GitHub issues](https://github.com/gerardog/gsudo/issues?q=) or create a new one if you have identified a problem. 17 | 18 | - Chat on [gitter](https://gitter.im/gsudo/) 19 | - Chat on [Discord](https://discord.com/invite/dEEA3P5WqF) 20 | 21 | ## Known issues 22 | 23 | - Do not install PowerShell as a .Net global tool (i.e. with `dotnet tool install --global PowerShell`), because it uses a shim tool with [unfixed issues](https://github.com/PowerShell/PowerShell/issues/11747). Install with any [other official method](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) instead, or with `choco install pwsh`, `winget install Microsoft.PowerShell`. 24 | 25 | - The elevated instances do not have access to the network shares connected on the non-elevated space. This is not a `gsudo` issue but how Windows works. Use `--copyNS` to replicate Network Shares into the elevated session, but this is not bi-directional and it's interactive (may prompt for user/password). 26 | 27 | - `gsudo.exe` can be placed on a network share and invoked as `\\server\share\gsudo {command}` but doesn't work if your **current** folder is a network drive. For example do not map `\\server\share\` to `Z:` and then `Z:\>gsudo do-something`. 28 | -------------------------------------------------------------------------------- /docs/docs/usage/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "How to Use", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/usage/mingw-msys2.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: bash-for-windows 3 | sidebar_position: 4 4 | hide_title: true 5 | title: Usage from Bash for Windows 6 | --- 7 | ## Usage from Bash for Windows (MinGW / MSYS2 / Git-Bash / Cygwin) 8 | 9 | Prepend `gsudo` to elevate `bash` commands (unless `-d` is used to elevate CMD commands). 10 | 11 | ## Bash Profile Config 12 | 13 | There is an issue with `bash for windows` shells: The process tree is split when invoking `bash` scripts such as the `gsudo` wrapper, that invalidates the Credentials Cache. 14 | You need to add this function to your `.bashrc` profile (to skip the wrapper): 15 | 16 | ```bash 17 | gsudo() { WSLENV=WSL_DISTRO_NAME:USER:$WSLENV MSYS_NO_PATHCONV=1 gsudo.exe "$@"; } 18 | ``` 19 | 20 | It looks like I missed a semi-colon `;` but I didn't. 21 | -------------------------------------------------------------------------------- /docs/docs/usage/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | id: usage 4 | title: How to Use 5 | hide_title: true 6 | --- 7 | ## How to Use 8 | 9 | **Note:** You can use anywhere **the `sudo` alias** created by the installers. 10 | 11 | ``` powershell 12 | gsudo [options] # Elevates your current shell 13 | gsudo [options] {command} [args] # Runs {command} with elevated permissions 14 | gsudo cache [on | off | help] # Starts/Stops an elevated cache session. (reduced UAC popups) 15 | gsudo status [--json | filter ] # Shows current user, cache and console status. 16 | gsudo !! # Re-run last command as admin. (YMMV) 17 | ``` 18 | 19 | ``` powershell 20 | General options: 21 | -n | --new # Starts the command in a new console (and returns immediately). 22 | -w | --wait # When in new console, wait for the command to end. 23 | --noexit # After running a command, keep the elevated shell open. 24 | --noclose # After running a command in a new console, ask for keypress before closing the console/window. 25 | 26 | Security options: 27 | -i | --integrity {v} # Specify integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System 28 | -u | --user {usr} # Run as the specified user. Asks for password. For local admins it shows a UAC unless '-i Medium' 29 | -s | --system # Run as Local System account (NT AUTHORITY\SYSTEM). 30 | --ti # Run as member of NT SERVICE\TrustedInstaller 31 | -k # Kills all cached credentials. The next time gsudo is run a UAC popup will be appear. 32 | 33 | Shell related options: 34 | -d | --direct # Skips Shell detection. Assume CMD shell or CMD {command}. 35 | --loadProfile # When elevating PowerShell commands, load user profile. 36 | 37 | Other options: 38 | --loglevel {val} # Set minimum log level to display: All, Debug, Info, Warning, Error, None 39 | --debug # Enable debug mode. 40 | --copyns # Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials 41 | --copyev # (deprecated) Copy environment variables to the elevated process. (not needed on default console mode) 42 | --chdir {dir} # Change the current directory to {dir} before running the command. 43 | 44 | ``` 45 | 46 | 47 | **Examples:** 48 | 49 | ``` powershell 50 | gsudo # elevates the current shell in the current console window (Supports Cmd/PowerShell/Pwsh Core/Yori/Take Command/git-bash/cygwin) 51 | gsudo -n # launch the current shell elevated in a new console window 52 | gsudo -n -w powershell ./Do-Something.ps1 # launch in new window and wait for exit 53 | gsudo notepad %windir%\system32\drivers\etc\hosts # launch windows app 54 | 55 | sudo notepad # sudo alias built-in 56 | 57 | # redirect/pipe input/output/error example 58 | gsudo dir | findstr /c:"bytes free" > FreeSpace.txt 59 | 60 | gsudo config LogLevel "Error" # Configure Reduced logging 61 | gsudo config Prompt "$P [elevated]$G " # Configure a custom Elevated Prompt 62 | gsudo config Prompt --reset # Reset to default value 63 | 64 | # Enable credentials cache (less UAC popups): 65 | gsudo config CacheMode Auto 66 | ``` 67 | 68 | ### Configuration 69 | 70 | 71 | ``` powershell 72 | gsudo config # Show current config settings & values. 73 | gsudo config {key} [--global] [value] # Read or write a user setting 74 | gsudo config {key} [--global] --reset # Reset config to default value 75 | --global # Affects all users (overrides user settings) 76 | ``` -------------------------------------------------------------------------------- /docs/docs/usage/wsl.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | title: Usage from WSL 4 | hide_title: true 5 | --- 6 | 7 | ## Usage from WSL 8 | 9 | On WSL (Windows Subsystem for Linux), elevation and `root` are different concepts. `root` allows full administration of WSL but not the Windows system. Use WSL's native `su` or `sudo` to gain `root` access. To get admin privilege on the Windows box you need to elevate the `WSL.EXE` process. `gsudo` allows that (a UAC popup will appear). 10 | 11 | On WSL bash, prepend `gsudo` to elevate **WSL commands** or `gsudo -d` for **CMD commands**. 12 | 13 | ``` bash 14 | # elevate default shell 15 | PC:~$ gsudo 16 | 17 | # run elevated WSL command 18 | PC:~$ gsudo mkdir /mnt/c/Windows/MyFolder 19 | 20 | # run elevated Windows command 21 | PC:~$ gsudo -d notepad C:/Windows/System32/drivers/etc/hosts 22 | PC:~$ gsudo -d "notepad C:\Windows\System32\drivers\etc\hosts" 23 | PC:~$ gsudo -d "echo 127.0.0.1 www.MyWeb.com >> %windir%\System32\drivers\etc\hosts" 24 | 25 | # test for gsudo and command success 26 | retval=$?; 27 | if [ $retval -eq 0 ]; then 28 | echo "Success"; 29 | elif [ $retval -eq $((999 % 256)) ]; then # gsudo failure exit code (999) is read as 231 on wsl (999 mod 256) 30 | echo "gsudo failed to elevate!"; 31 | else 32 | echo "Command failed with exit code $retval"; 33 | fi; 34 | ``` -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Easy to Use', link: "docs/usage", 8 | description: ( 9 | <> 10 | Prepend gsudo to make your command run elevated in the current console window. Just as Unix/Linux sudo. One UAC popup will appear. 11 |
Learn more 12 | 13 | ), 14 | }, { 15 | title: 'Easy to Install', link: "docs/install", 16 | description: ( 17 | <> 18 | Using Chocolatey: choco install gsudo
19 | Using Scoop: scoop install gsudo
20 | Using WinGet: winget install gerardog.gsudo
21 | Learn how to install. 22 | 23 | ), 24 | }, { 25 | title: 'Portable', 26 | description: ( 27 | <> 28 | gsudo is just a portable console app. No Windows service is required or system change is done, except adding gsudo to the Path. 29 | 30 | ), 31 | }, { 32 | title: 'Supports your preferred shell', 33 | description: ( 34 | <> 35 | Detects your shell and elevates your command as a native shell command. 36 | Currently supports CMD, PowerShell, WSL, MinGW/Git-Bash, MSYS2, Cygwin, Nushell, Yori and Take Command. 37 | 38 | ), 39 | }, { 40 | title: 'Credentials Cache', link: "docs/credentials-cache", 41 | description: ( 42 | <> 43 | Too many UAC pop-ups? You can see less popups if you opt-in to enable the credentials cache, once you understand the security implications. 44 |
Learn more. 45 | 46 | ), 47 | }, { 48 | title: 'Increase your productivity', 49 | description: ( 50 | <> 51 | Do not waste time opening a new window and switching context back and forth. Also avoid the "elevation fatigue" that leads to the malpractice of running everything elevated, and just elevate specific commands. 52 | 53 | ), 54 | }, 55 | ]; 56 | 57 | function Feature({Svg, title, description, link}) { 58 | return ( 59 |
60 | {/* */} 65 |
66 |

{title}

67 |

{description}

68 |
69 |
70 | ); 71 | } 72 | 73 | export default function HomepageFeatures() { 74 | return ( 75 |
76 |
77 |
78 | {FeatureList.map((props, idx) => ( 79 | 80 | ))} 81 |
82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | } 18 | 19 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 20 | [data-theme='dark'] { 21 | --ifm-color-primary: #25c2a0; 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary: #7c85dd; 24 | --ifm-color-primary: #9aa4ff; 25 | /**/ 26 | /*--ifm-color-primary: #af3830;*/ 27 | --ifm-color-content-inverse: --ifm-font-color-base; 28 | --ifm-background-image: linear-gradient(315deg, #f9d29d 0%, #ffd8cb 74%); 29 | --ifm-color-primary-dark: #21af90; 30 | --ifm-color-primary-darker: #1fa588; 31 | --ifm-color-primary-darkest: #1a8870; 32 | --ifm-color-primary-light: #29d5b0; 33 | --ifm-color-primary-lighter: #32d8b4; 34 | --ifm-color-primary-lightest: #4fddbf; 35 | } 36 | 37 | .docusaurus-highlight-code-line { 38 | background-color: rgba(0, 0, 0, 0.1); 39 | display: block; 40 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 41 | padding: 0 var(--ifm-pre-padding); 42 | } 43 | 44 | [data-theme='dark'] .docusaurus-highlight-code-line { 45 | background-color: rgba(0, 0, 0, 0.3); 46 | } 47 | 48 | code { 49 | vertical-align:baseline; 50 | } 51 | 52 | .demo-image { 53 | clip-path: inset(1px 10px 0px 1px round 10px); 54 | margin-top: 2em; 55 | } -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 8 | import DemoImage from '../../../demo.gif' 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

gsudo (sudo for Windows)

16 |

{siteConfig.tagline}

17 |
18 | 21 | Read the docs 22 |   23 | 26 | Install 27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 | ); 35 | } 36 | 37 | export default function Home() { 38 | const {siteConfig} = useDocusaurusContext(); 39 | return ( 40 | 43 | 44 |
45 | 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/src/pages/sponsor.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide_title: true 3 | title: Sponsor 4 | --- 5 | 6 | ## Sponsor gsudo 7 | 8 | Hi, I'm Gerardo, a seasoned software developer from Argentina with a journey in programming that began at the age of 15. My passion for technology led me to create 'gsudo', an open-source project that enhances the windows desktop. 9 | 10 | Through sponsorship, you can help sustain the ongoing development of 'gsudo', including keeping it updated, adding new features, and potentially kickstarting new projects. Your support will address practical needs like covering code-signing certificate costs and other expenses, enabling me to focus on innovation and development. 11 | 12 | `gsudo` is delivered as a signed application. This ensures the UAC popup doesn't show "Untrusted Publisher", and protects end users detect that gsudo binaries hadn´t been altered. 13 | 14 | On Nov 2022, [Parag Mehta @parag_c_mehta](https://twitter.com/gerardo_gr/status/1595817336405168134) gifted me a code signing certificate which expires in Nov 2025. We have some months left to collect $632 for another [3 year renewal](https://signmycode.com/code-signing-certificates). 15 | 16 | How to donate: 17 | 18 | 💵 [Via GitHub Sponsors](https://github.com/sponsors/gerardog) 19 | 20 | 💵 One time, or recurring donations via [PayPal or Debit/Credit Card](https://www.paypal.com/donate/?business=EGM43RDQ4VRWJ&no_recurring=0&item_name=%0APlease+help+me+dedicate+more+time+to+gsudo%21%0AIf+gsudo+has+improved+your+productivity%2C+please+contribute%21¤cy_code=USD) 21 | 22 | 💵 BTC 33LVf9Ek9hg5dQYf1EFWUoJaF5W3tWrrhZ 23 | 24 | 💵 ETH/USDC/USDT/SHIB 0x3c207f47a80b6feede8e9546ea1ca68635652452 25 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/AnimatedPrompt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/AnimatedPrompt.gif -------------------------------------------------------------------------------- /docs/static/img/NewWindow.CloseBehaviour.KeepShellOpen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/NewWindow.CloseBehaviour.KeepShellOpen.png -------------------------------------------------------------------------------- /docs/static/img/NewWindow.CloseBehaviour.PressKeyToClose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/NewWindow.CloseBehaviour.PressKeyToClose.png -------------------------------------------------------------------------------- /docs/static/img/Vista-UAC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/Vista-UAC.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/gsudo-anim1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/gsudo-anim1.png -------------------------------------------------------------------------------- /docs/static/img/gsudo-anim2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/gsudo-anim2.png -------------------------------------------------------------------------------- /docs/static/img/gsudo-powershell-prompt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/gsudo-powershell-prompt.gif -------------------------------------------------------------------------------- /docs/static/img/gsudo-powershell-prompt.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/gsudo-powershell-prompt.mp4 -------------------------------------------------------------------------------- /docs/static/img/gsudo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/docs/static/img/gsudo.png -------------------------------------------------------------------------------- /installgsudo.ps1: -------------------------------------------------------------------------------- 1 | if ([System.Environment]::GetEnvironmentVariable("PROCESSOR_ARCHITECTURE", "Machine") -eq "ARM64") { 2 | $architecture='arm64' 3 | } elseif (! [System.Environment]::Is64BitOperatingSystem) { 4 | $architecture='x86' 5 | } else { 6 | $architecture='x64' 7 | } 8 | 9 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 10 | $release = Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/gerardog/gsudo/releases/latest" 11 | $asset = $release.assets | Where-Object name -like "gsudo.setup.$architecture.msi" 12 | $fileName = "$env:TEMP\$($asset.name)" 13 | 14 | Write-Output "Downloading $($asset.name)" 15 | Invoke-RestMethod -Method Get -Uri $asset.browser_download_url -OutFile $fileName 16 | 17 | Write-Output "Installing $($asset.name)" 18 | 19 | $DataStamp = get-date -Format yyyyMMddTHHmmss 20 | $logFile = '{0}-{1}.log' -f "$env:TEMP\gsudoSetup",$DataStamp 21 | 22 | $MSIArguments = @( 23 | "/i" 24 | ('"{0}"' -f $fileName) 25 | "/qb" 26 | "/norestart" 27 | "/L*v" 28 | $logFile 29 | ) 30 | $msiexec = (Get-Command "msiexec.exe").Path 31 | $process = Start-Process -ArgumentList $MSIArguments -Wait $msiexec -PassThru 32 | 33 | if ($process.ExitCode -ne 0) 34 | { 35 | #Get-Content $logFile 36 | Write-Warning -Verbose "Installation failed! (msiexec error code $($process.ExitCode))" 37 | Write-Warning -Verbose " Log File location: $logFile" 38 | Write-Warning -Verbose " MSI File location: $fileName" 39 | } 40 | else 41 | { 42 | New-Item -Type Directory ($PROFILE | Split-Path) -ErrorAction SilentlyContinue 43 | 44 | Write-Output "gsudo installed succesfully!" 45 | Write-Output "Please restart your consoles to use gsudo!`n" 46 | 47 | "PowerShell users: To use enhanced gsudo and Invoke-Gsudo cmdlet, add the following line to your `$PROFILE" 48 | " Import-Module 'gsudoModule'" 49 | "Or run: " 50 | " Write-Output `"``nImport-Module 'gsudoModule'`" | Add-Content `$PROFILE" 51 | 52 | Remove-Item $fileName 53 | } 54 | 55 | if ([Console]::IsInputRedirected -eq $false -and [Console]::IsOutputRedirected -eq $false) 56 | { 57 | Write-Host -NoNewLine 'Press any key to continue...'; 58 | $_ = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'); 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start docs", 8 | "build": "docusaurus build docs", 9 | "swizzle": "docusaurus swizzle docs", 10 | "deploy": "docusaurus deploy docs", 11 | "clear": "docusaurus clear docs", 12 | "serve": "docusaurus serve docs", 13 | "write-translations": "docusaurus write-translations docs", 14 | "write-heading-ids": "docusaurus write-heading-ids docs" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^2.4.1", 18 | "@docusaurus/plugin-google-gtag": "^2.4.3", 19 | "@docusaurus/plugin-sitemap": "^2.4.3", 20 | "@docusaurus/preset-classic": "^2.4.1", 21 | "@mdx-js/react": "^1.6.22", 22 | "clsx": "^1.1.1", 23 | "prism-react-renderer": "^1.3.1", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.5%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample-scripts/many-elevations-using-gsudo-cache.cmd: -------------------------------------------------------------------------------- 1 | :: This script demonstrates how to use gsudo cache to 2 | :: perform a mix of unelevated and elevated tasks 3 | :: showing only one UAC popup. 4 | 5 | @echo off 6 | 7 | echo. 8 | echo This is a Demo Computer Clean-up script designed to be executed monthly or at intervals of a few months. 9 | echo WARNING: THIS SCRIPT WILL DELETE YOUR TEMPORARY FILES! Press Ctrl-C to abort or reject the User Access Control window! 10 | pause 11 | 12 | :: Start a cache. with max idle time of 5 minutes. 13 | gsudo --loglevel Error cache on --duration 00:05:00 14 | if errorlevel 999 echo Failed to elevate. Aborting Script & exit /b 15 | 16 | echo Cleaning up Windows temporary files... (Elevation required) 17 | gsudo del /s /q "C:\Windows\Temp\*.*" 18 | 19 | echo. 20 | echo Cleaning up current user temporary files... (No elevation required) 21 | for /d %%x in ("%USERPROFILE%\AppData\Local\Temp\*") do @rd /s /q "%%x" 22 | 23 | echo. 24 | echo Cleaning up Internet Explorer cache... (No elevation required) 25 | for /d %%x in ("%USERPROFILE%\AppData\Local\Microsoft\Windows\INetCache\*") do @rd /s /q "%%x" 26 | 27 | :: echo. 28 | :: echo Emptying the Recycle Bin for the all users: 29 | :: gsudo del /s /q "%SystemDrive%\$Recycle.Bin\*.*" 30 | 31 | echo. 32 | echo Clean-up completed successfully! 33 | 34 | :: close gsudo cache, silently 35 | gsudo --loglevel Error cache off 36 | 37 | -------------------------------------------------------------------------------- /sample-scripts/many-elevations-using-gsudo-cache.ps1: -------------------------------------------------------------------------------- 1 | # This script demonstrates how to use gsudo cache to 2 | # perform a mix of unelevated and elevated tasks 3 | # showing only one UAC popup. 4 | 5 | Write-Host "This is a Demo Computer Clean-up script designed to be executed monthly or at intervals of a few months. 6 | WARNING: THIS SCRIPT WILL DELETE YOUR TEMPORARY FILES! Press Ctrl-C to abort or reject the User Access Control window!" -ForegroundColor Yellow 7 | Read-Host "Press Enter to continue..." 8 | 9 | # Start a cache. with max idle time of 5 minutes. 10 | gsudo --loglevel Error cache on --duration "00:05:00" 11 | if ($LASTEXITCODE -ge 999) { Write-Host "Failed to elevate. Aborting Script."; exit } 12 | 13 | # Clear the temporary files 14 | Write-Host "Cleaning up Windows temporary files... (Elevation required)" -ForegroundColor Yellow 15 | gsudo { 16 | Get-ChildItem "C:\Windows\Temp\*" | Remove-Item -Force -ErrorAction SilentlyContinue -Recurse 17 | Get-ChildItem "C:\Users\*\AppData\Local\Temp\*" | Remove-Item -Force -ErrorAction SilentlyContinue -Recurse 18 | } 19 | 20 | # Clear the Internet Explorer cache 21 | Write-Host "Cleaning up Internet Explorer cache..." -ForegroundColor Yellow 22 | gsudo { 23 | Remove-Item "C:\Users\*\AppData\Local\Microsoft\Windows\INetCache\*" -Recurse -ErrorAction SilentlyContinue 24 | } 25 | 26 | # Write-Host "`nEmptying the Recycle Bin for all users:" -ForegroundColor Yellow 27 | # gsudo { Remove-Item -Path "$env:SystemDrive\$Recycle.Bin\*.*" -Recurse -Force } 28 | 29 | Write-Host "Clean-up completed successfully!`n" -ForegroundColor Yellow 30 | 31 | # close gsudo cache, silently 32 | gsudo --loglevel Error cache off -------------------------------------------------------------------------------- /sample-scripts/readme.md: -------------------------------------------------------------------------------- 1 | # gsudo Sample Scripts 2 | 3 | This folder contains sample scripts demonstrating how to use `gsudo` to elevate privileges on a Windows machine. The provided scripts include: 4 | 5 | 1. **Script Self-Elevation:** A technique to ensure that the current script is running elevated, by detecting when it is not, and elevating itself. More details can be found in the [gsudo documentation on script self-elevation](https://gerardog.github.io/gsudo/docs/tips/script-self-elevation). 6 | 7 | - [`self-elevate.cmd`](./self-elevate.cmd): Batch script version 8 | - [`self-elevate-one-liner.cmd`](./self-elevate-one-liner.cmd): A one-liner version of the batch script. 9 | - [`self-elevate.ps1`](./self-elevate.ps1): PowerShell version. 10 | - [`self-elevate-without-gsudo.cmd`](./self-elevate-without-gsudo.cmd): Example without using `gsudo`. 11 | 12 | 2. **Many Elevations Using gsudo Cache:** 13 | 14 | - [`many-elevations-using-gsudo-cache.cmd`](./many-elevations-using-gsudo-cache.cmd): Batch script version 15 | - [`many-elevations-using-gsudo-cache.ps1`](./many-elevations-using-gsudo-cache.ps1): PowerShell version 16 | 17 | 3. **Don't show an UAC pop-up:** Perform an elevated admin task if and only if it can be done without an interactive UAC pop-up. (i.e. when already elevated or gsudo cache is active) 18 | - [`silent-elevation-one-liner.cmd`](./silent-elevation-one-liner.cmd): A one-liner version. 19 | - [`silent-elevation.cmd`](./silent-elevation.cmd): Verbose version . 20 | 21 | These scripts are examples and should be used with caution. Always review and understand the code you are executing on your system. 22 | -------------------------------------------------------------------------------- /sample-scripts/self-elevate-one-liner.cmd: -------------------------------------------------------------------------------- 1 | @gsudo status IsElevated --no-output || (gsudo "%~f0" & exit /b) 2 | :: You are elevated here. Do admin stuff. 3 | -------------------------------------------------------------------------------- /sample-scripts/self-elevate-without-gsudo.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: This script performs self-elevation, in a new console using only built-in windows tools. 3 | 4 | :: detect elevation using 'net session', jump to :ElevatedTasks if we are admin. 5 | net session >nul 2>nul & net session >nul 2>nul && goto :ElevatedTasks 6 | echo Admin rights needed. Elevating using powershell... 7 | 8 | :: This powershell command re-executes this script a new elevated console 9 | powershell -C start-Process "%~f0" -Verb RunAs -ArgumentList \"%* \"" 10 | 11 | :: To make the caller script wait for the elevated tasks to end add the "-Wait" option, like: 12 | :: powershell -C start-Process "%~f0" -Verb RunAs -ArgumentList \"%* \"" -Wait 13 | 14 | IF ERRORLEVEL 1 Echo Elevation failed! 15 | 16 | :: make the unelevated script end. 17 | exit /b 18 | 19 | :ElevatedTasks 20 | :: You are elevated here. Add your admin tasks here. 21 | :: This will run as admin :: 22 | 23 | :: For demo purposes, show I am elevated: (a.k.a. High Mandatory Level) 24 | whoami /groups | findstr /i "S-1-16-" 25 | 26 | :: For demo purposes, prevent the elevated window from closing too fast: 27 | pause -------------------------------------------------------------------------------- /sample-scripts/self-elevate.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | gsudo status IsElevated --no-output && goto :IsElevated 3 | 4 | echo Admin rights needed. Elevating using gsudo. 5 | gsudo "%~f0" %* 6 | if errorlevel 999 Echo Failed to elevate! 7 | exit /b %errorlevel% 8 | 9 | :IsElevated 10 | :: You are elevated here. Do admin stuff. 11 | -------------------------------------------------------------------------------- /sample-scripts/self-elevate.ps1: -------------------------------------------------------------------------------- 1 | function Test-IsAdmin { 2 | return (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 3 | } 4 | 5 | if ((Test-IsAdmin) -eq $false) { 6 | Write-Warning "This script requires local admin privileges. Elevating..." 7 | gsudo "& '$($MyInvocation.MyCommand.Source)'" $args 8 | if ($LastExitCode -eq 999 ) { 9 | Write-error 'Failed to elevate.' 10 | } 11 | return 12 | } 13 | 14 | # You are elevated here. Do admin stuff. -------------------------------------------------------------------------------- /sample-scripts/silent-elevation-one-liner.cmd: -------------------------------------------------------------------------------- 1 | :: This script demonstrates how to use gsudo to perform an elevated admin task 2 | :: if and only if it can be done without an interactive UAC pop-up 3 | @(gsudo status IsElevated --no-output || gsudo status CacheAvailable --no-output) || (echo Running as non admin and no gsudo cache available. Aborting... && exit /b) 4 | 5 | :: Write your task here. 6 | 7 | :: It's not guaranted to be running as admin! But prepend gsudo to elevate a command without a UAC, by using the existing cache. 8 | 9 | :: For example, elevate gsudo status, using gsudo, to get the status after elevation. 10 | gsudo "gsudo status" 11 | -------------------------------------------------------------------------------- /sample-scripts/silent-elevation.cmd: -------------------------------------------------------------------------------- 1 | :: This script demonstrates how to use gsudo to perform an elevated admin task 2 | :: if and only if it can be done without an interactive UAC pop-up 3 | 4 | @echo off 5 | 6 | :: Set this value to the worst possible execution time of this script 7 | set CACHE_DURATION=00:01:00 8 | 9 | :: If we are elevated, we can do admin stuff directly. 10 | gsudo status IsElevated --no-output && Echo - Running as Admin? Yes && goto :run-script 11 | Echo - Running as Admin? No 12 | 13 | :: Check if there is a gsudo credentials cache available, so elevation is possible. 14 | gsudo status CacheAvailable --no-output || echo - No gsudo cache available: Aborting.. && exit /b 15 | 16 | :: Extending cache duration for the max amount of time this script could run to ensure the cache remains. 17 | Echo - Found gsudo cache available: Extending cache duration to ensure cache is available during all the execution of this script. 18 | set using-cache=1 19 | 20 | gsudo --loglevel error Cache on --duration %CACHE_DURATION% 21 | if errorlevel 0 echo - Extended gsudo cache duration successfully && goto :run-script 22 | if errorlevel 1 echo - Failed to extend gsudo cache duration. Aborting... && exit /b 23 | 24 | :run-script 25 | 26 | echo - Executing script 27 | echo. 28 | :: Write your task here. 29 | :: It's not running as admin: Prepend gsudo to elevate a command using the existing cache. 30 | 31 | :: For example here I am elevating a call to gsudo status, getting the elevation status after elevation. 32 | gsudo gsudo status 33 | 34 | :: Cleanup 35 | if "%using-cache%"=="1" echo - Stopping gsudo cache... 36 | if "%using-cache%"=="1" gsudo --loglevel none cache off 37 | set using-cache= 38 | -------------------------------------------------------------------------------- /src/KeyPressTester/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/KeyPressTester/KeyPressTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | Exe 5 | false 6 | Debug;Release;Debug-Net46 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/KeyPressTester/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace KeyPressTester 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | Console.ForegroundColor = ConsoleColor.White; 14 | while(true) 15 | { 16 | var key = Console.ReadKey(true); 17 | 18 | bool IsControl = key.Modifiers.HasFlag(ConsoleModifiers.Control); 19 | bool IsShift = key.Modifiers.HasFlag(ConsoleModifiers.Shift); 20 | bool IsAlt = key.Modifiers.HasFlag(ConsoleModifiers.Alt); 21 | 22 | char modifier = '1'; 23 | if (IsShift) modifier = (char)(modifier + 1); 24 | if (IsAlt) modifier = (char)(modifier + 2); 25 | if (IsControl) modifier = (char)(modifier + 4); 26 | 27 | Console.Write( $"KeyPressTester: Modifier={modifier} Key={key.Key.ToString()} keyChar={key.KeyChar} => "); 28 | 29 | if (key.Modifiers.HasFlag(ConsoleModifiers.Control)) 30 | Console.Write($"Control + "); 31 | if (key.Modifiers.HasFlag(ConsoleModifiers.Alt)) 32 | Console.Write($"Alt + "); 33 | if (key.Modifiers.HasFlag(ConsoleModifiers.Shift)) 34 | Console.Write($"Shift + "); 35 | 36 | Console.WriteLine(key.Key.ToString()); 37 | 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/KeyPressTester/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("KeyPressTester")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("KeyPressTester")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fd3b76fd-e682-4c65-a936-84b9b2c6fb19")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/gsudo.Installer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33516.290 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "gsudomsi", "gsudo.Installer\gsudomsi.wixproj", "{34AAA3CF-95E5-41EA-8A00-C5F713BF664E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|ARM64.ActiveCfg = Debug|ARM64 19 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|ARM64.Build.0 = Debug|ARM64 20 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|x64.ActiveCfg = Debug|x64 21 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|x64.Build.0 = Debug|x64 22 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|x86.ActiveCfg = Debug|x86 23 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Debug|x86.Build.0 = Debug|x86 24 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|ARM64.ActiveCfg = Release|ARM64 25 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|ARM64.Build.0 = Release|ARM64 26 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|x64.ActiveCfg = Release|x64 27 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|x64.Build.0 = Release|x64 28 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|x86.ActiveCfg = Release|x86 29 | {34AAA3CF-95E5-41EA-8A00-C5F713BF664E}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {E57E52F3-716F-4EE1-8339-3A1A96207D24} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/gsudo.Installer/.gitignore: -------------------------------------------------------------------------------- 1 | Constants.wxi -------------------------------------------------------------------------------- /src/gsudo.Installer/Constants.Template.wxi: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/gsudo.Installer/gsudomsi.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | gsudo.setup.$(Platform) 4 | 5 | 6 | Debug 7 | 8 | 9 | Release 10 | 11 | 12 | Debug 13 | 14 | 15 | Release 16 | 17 | 18 | Debug 19 | 20 | 21 | Release 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/gsudo.Installer/vendor/LICENSE.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 System;}} 2 | {\*\generator Riched20 10.0.19041}\viewkind4\uc1 3 | \pard\sa200\sl240\slmult1\b\f0\fs22\lang9 MIT License\par 4 | \par 5 | Copyright (c) 2019 Gerardo Grignoli\par 6 | \par 7 | Permission is hereby granted, free of charge, to any person obtaining a copy\par 8 | of this software and associated documentation files (the "Software"), to deal\par 9 | in the Software without restriction, including without limitation the rights\par 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\par 11 | copies of the Software, and to permit persons to whom the Software is\par 12 | furnished to do so, subject to the following conditions:\par 13 | \par 14 | The above copyright notice and this permission notice shall be included in all\par 15 | copies or substantial portions of the Software.\par 16 | \par 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\par 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\par 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\par 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\par 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\par 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\par 23 | SOFTWARE.\par 24 | } 25 | -------------------------------------------------------------------------------- /src/gsudo.Installer/vendor/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gerardo Grignoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/gsudo.Tests/ArgumentParsingTests.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace gsudo.Tests 10 | { 11 | [TestClass] 12 | public class ArgumentParsingTests 13 | { 14 | [TestMethod] 15 | public void Arguments_QuotedTests() 16 | { 17 | var input = "\"my exe name\" \"my params\" OtherParam1 OtherParam2 OtherParam3"; 18 | var expected = new string[] { "\"my exe name\"", "\"my params\"", "OtherParam1", "OtherParam2", "OtherParam3" }; 19 | 20 | var actual = ArgumentsHelper.SplitArgs(input).ToArray(); 21 | 22 | Assert.AreEqual(expected.Length, actual.Length); 23 | 24 | for (int i = 0; i < expected.Length; i++) 25 | Assert.AreEqual(expected[i], actual[i]); 26 | } 27 | 28 | [TestMethod] 29 | public void Arguments_NoQuotesTests() 30 | { 31 | var input = "HEllo I Am my params OtherParam1 OtherParam2 OtherParam3"; 32 | var expected = new string[] { "HEllo", "I", "Am", "my", "params", "OtherParam1", "OtherParam2", "OtherParam3" }; 33 | 34 | var actual = ArgumentsHelper.SplitArgs(input).ToArray(); 35 | 36 | Assert.AreEqual(expected.Length, actual.Length); 37 | 38 | for (int i = 0; i < expected.Length; i++) 39 | Assert.AreEqual(expected[i], actual[i]); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/gsudo.Tests/AssertExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace gsudo.Tests 5 | { 6 | static class AssertExtensions 7 | { 8 | internal static string AssertHasLine(this string input, string lineToFind) 9 | { 10 | var sr = new StringReader(input); 11 | string inputLine; 12 | while ((inputLine = sr.ReadLine()) != null) 13 | { 14 | if (inputLine == lineToFind) 15 | return sr.ReadToEnd(); 16 | } 17 | 18 | Assert.Fail($"Input does not contain \"{lineToFind}\"\r\nInput was:\r\n{input}"); 19 | return null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/gsudo.Tests/PowershellTests.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Commands; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | 5 | namespace gsudo.Tests 6 | { 7 | [TestClass] 8 | public class PowerShellCoreTests : PowerShellTests 9 | { 10 | [ClassInitialize] 11 | public static new void ClassInitialize(TestContext context) => TestShared.StartCacheSession(); 12 | 13 | public PowerShellCoreTests() 14 | { 15 | PS_FILENAME = "pwsh.exe"; 16 | } 17 | } 18 | 19 | [TestClass] 20 | public class PowerShellCoreAttachedTests : PowerShellTests 21 | { 22 | public PowerShellCoreAttachedTests() 23 | { 24 | GSUDO_ARGS = "--attached "; 25 | } 26 | } 27 | 28 | [TestClass] 29 | public class PowerShellTests 30 | { 31 | [ClassInitialize] 32 | public static void ClassInitialize(TestContext context) => TestShared.StartCacheSession(); 33 | 34 | internal string PS_FILENAME = "PowerShell.exe"; 35 | internal string PS_ARGS = "-NoExit -NoLogo -NoProfile -Command Set-ExecutionPolicy UnRestricted -Scope CurrentUser; function Prompt { return '# '}"; 36 | internal string GSUDO_ARGS = ""; 37 | 38 | static PowerShellTests() 39 | { 40 | Environment.SetEnvironmentVariable("PROMPT", "$G"); // Remove path from prompt so tests results are invariant of the src folder location in the path. 41 | } 42 | 43 | //[TestMethod] 44 | public void Debug() 45 | { 46 | var p = System.Diagnostics.Process.Start($"{PS_FILENAME} {PS_ARGS}"); 47 | } 48 | 49 | [TestMethod] 50 | public void PS_CommandLineEchoSingleQuotesTest() 51 | { 52 | var p = new TestProcess($"gsudo {GSUDO_ARGS}powershell -noprofile -NoLogo -command echo 1 '2 3'"); 53 | p.WaitForExit(); 54 | p.GetStdOut() 55 | .AssertHasLine("1") 56 | .AssertHasLine("2 3"); 57 | Assert.AreEqual(0, p.ExitCode); 58 | } 59 | 60 | [TestMethod] 61 | public void PS_CommandLineEchoDoubleQuotesTest() 62 | { 63 | var p = new TestProcess($"gsudo {GSUDO_ARGS}powershell -noprofile -NoLogo -command echo 1 '\\\"2 3\\\"'"); 64 | p.WaitForExit(); 65 | p.GetStdOut() 66 | .AssertHasLine("1") 67 | .AssertHasLine("\"2 3\""); 68 | Assert.AreEqual(0, p.ExitCode); 69 | } 70 | 71 | [TestMethod] 72 | public void PS_EchoNoQuotesTest() 73 | { 74 | var p = new TestProcess( 75 | $@"./gsudo {GSUDO_ARGS}'echo 1 2 3' 76 | exit 77 | ", $"{PS_FILENAME} {PS_ARGS}"); 78 | p.WaitForExit(); 79 | 80 | p.GetStdOut() 81 | .AssertHasLine("1") 82 | .AssertHasLine("2") 83 | .AssertHasLine("3"); 84 | 85 | Assert.AreEqual(0, p.ExitCode); 86 | } 87 | 88 | [TestMethod] 89 | public void PS_EchoSingleQuotesTest() 90 | { 91 | var p = new TestProcess($"./gsudo {GSUDO_ARGS}'echo 1 ''2 3'''\r\nexit\r\n", $"{PS_FILENAME} {PS_ARGS}"); 92 | 93 | p.WaitForExit(); 94 | 95 | p.GetStdOut() 96 | .AssertHasLine("1") 97 | .AssertHasLine("2 3") 98 | ; 99 | Assert.AreEqual(0, p.ExitCode); 100 | } 101 | 102 | [TestMethod] 103 | public virtual void PS_EchoDoubleQuotesTest() 104 | { 105 | var p = new TestProcess($"./gsudo {GSUDO_ARGS}'echo 1 \"2 3\"'\r\nexit", $"{PS_FILENAME} {PS_ARGS}"); 106 | p.WaitForExit(); 107 | p.GetStdOut() 108 | .AssertHasLine("1") 109 | .AssertHasLine("2 3") 110 | ; 111 | Assert.AreEqual(0, p.ExitCode); 112 | } 113 | 114 | 115 | [TestMethod] 116 | public void PS_WriteProgress() 117 | { 118 | var p = new TestProcess($"{PS_FILENAME} {PS_ARGS}\r\n./gsudo {GSUDO_ARGS}Write-Progress -Activity \"Test\"; exit\r\n"); 119 | p.WaitForExit(); 120 | Assert.IsFalse(p.GetStdOut().Contains("internal error", StringComparison.OrdinalIgnoreCase)); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/gsudo.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("gsudo.Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("gsudo.Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2019")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("b91748fc-950f-46ab-a037-7118bea81ff7")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/gsudo.Tests/gsudo.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | false 5 | Debug;Release 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/gsudo.Wrappers.Tests/Invoke-gsudo.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe "PS Invoke-Gsudo (PSv$($PSVersionTable.PSVersion.Major))" { 2 | BeforeAll { 3 | $env:Path = (Get-Item (Join-Path $PSScriptRoot "..\gsudo.Wrappers")).FullName + ";" + $env:Path 4 | } 5 | 6 | BeforeEach { 7 | $global:ErrorActionPreference=$ErrorActionPreference='Continue'; 8 | } 9 | 10 | It "It serializes return values maintaining its type" { 11 | $result = Invoke-gsudo { 1+1 } 12 | $result | Should -Be 2 13 | $result -is [System.Int32] | Should -Be $true 14 | } 15 | 16 | It "It serializes return values mantaining its properties." { 17 | $result = Invoke-Gsudo { Get-Date } 18 | $result.Year | Should -Not -BeNullOrEmpty 19 | } 20 | 21 | It "It returns an array of values mantaining its properties." { 22 | $result = Invoke-Gsudo { @( 23 | [PSCustomObject]@{ First = 'John' ; Last = 'Smith' } 24 | [PSCustomObject]@{ First = 'Peter' ; Last = 'Smith' } 25 | ) } 26 | $result.Count | Should -Be 2 27 | $result[0].First | Should -Be 'John' 28 | $result[1].First | Should -Be 'Peter' 29 | } 30 | 31 | It "It accepts objects from the pipeline." { 32 | $currentDate = Invoke-Gsudo { Get-Date } 33 | $currentDate.Year | Should -Be (Get-Date).Year 34 | } 35 | 36 | It "It throws when Error thrown" { 37 | { Invoke-gsudo { throw } } | Should -throw "ScriptHalted" 38 | } 39 | 40 | It "It throws with expression runtime errors" { 41 | { Invoke-gsudo { 0/0 } } | Should -throw "Attempted to divide by zero." 42 | { Invoke-gsudo { Get-InvalidCmdLet } } | Should -throw "*is not recognized*" 43 | } 44 | 45 | It "It throws with .Net Exceptions" { 46 | { Invoke-gsudo { [int]::Parse('foo') } } | Should -throw "*nput string*" 47 | } 48 | 49 | It "It throws when ErrorAction = Stop" { 50 | { Invoke-gsudo { Get-Item "\non-existent" -ErrorAction Stop } } | Should -throw "Cannot find path*" 51 | } 52 | 53 | It "It throws when ErrorActionPreference = Stop" { 54 | { 55 | Invoke-gsudo { $ErrorActionPreference = "Stop"; Get-Item "\non-existent" } 56 | } | Should -throw 57 | } 58 | 59 | It "It forwards ErrorActionPreference '' to the elevated instance" -TestCases @( 60 | @{ ea = 'Stop' } 61 | @{ ea = 'Continue' } 62 | @{ ea = 'Ignore' } 63 | ) { 64 | param ($ea) 65 | $global:ErrorActionPreference = $ErrorActionPreference = $ea; Invoke-gsudo { "$ErrorActionPreference" } | Should -be $ea 66 | } 67 | 68 | It "It doesn't throw when ErrorActionPreference = Continue" { 69 | {Invoke-gsudo { "\non-existent" | Get-Item }} | Should -not -throw 70 | {"\non-existent" | Invoke-gsudo { Get-Item }} | Should -not -throw 71 | } 72 | 73 | It "It doesn't throw with '-ErrorAction Continue-'" { 74 | $ErrorActionPreference = "Stop"; 75 | 76 | {Invoke-gsudo { "\non-existent" | Get-Item -ErrorAction Continue}} | Should -not -throw 77 | {"\non-existent" | Invoke-gsudo { Get-Item -ErrorAction Continue}} | Should -not -throw 78 | 79 | {Invoke-gsudo { "\non-existent" | Get-Item } -ErrorAction Continue} | Should -not -throw 80 | {"\non-existent" | Invoke-gsudo { Get-Item } -ErrorAction Continue} | Should -not -throw 81 | 82 | $ErrorActionPreference = "Continue"; 83 | {Invoke-gsudo { "\non-existent" | Get-Item }} | Should -not -throw 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/gsudo.Wrappers.Tests/gsudo.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe "PS Gsudo (PSv$($PSVersionTable.PSVersion.Major))" { 2 | BeforeAll { 3 | $env:Path = (Get-Item (Join-Path $PSScriptRoot "..\gsudo.Wrappers")).FullName + ";" + $env:Path 4 | $Path = (Get-Item (Join-Path $PSScriptRoot "..\gsudo.Wrappers\gsudoModule.psm1")).FullName 5 | $Path | Should -not -BeNullOrEmpty 6 | Import-Module $Path 7 | } 8 | 9 | BeforeEach { 10 | $ErrorActionPreference='Continue' 11 | } 12 | 13 | It "It serializes return values as string." { 14 | $result = gsudo "1+1" 15 | $result | Should -Be "2" 16 | $result -is [System.String] | Should -Be $true 17 | } 18 | 19 | It "When invoked as `gsudo !!`, It elevates the last command executed" { 20 | 21 | @" 22 | #TYPE Microsoft.PowerShell.Commands.HistoryInfo 23 | "Id","CommandLine","ExecutionStatus","StartExecutionTime","EndExecutionTime","Duration" 24 | "1","Write-Output 'Hello World'","Completed","2/2/2022 12:13:11 PM","2/2/2022 12:13:11 PM","00:00:00.0421414" 25 | "@ | ConvertFrom-Csv | Add-History -ErrorAction stop 26 | 27 | gsudo !! | Should -Be 'Hello World' 28 | } 29 | 30 | It "Elevates a ScriptBlock." { 31 | $result = gsudo { (1+1) } 32 | $result | Should -Be "2" 33 | $result -is [System.Int32] | Should -Be $true 34 | } 35 | 36 | It "Elevates a ScriptBlock with arguments." { 37 | $result = gsudo { "$($args[1]) $($args[0])" } -args "World", "Hello" 38 | $result | Should -Be "Hello World" 39 | $result -is [System.String] | Should -Be $true 40 | } 41 | 42 | It "Return can be captured." { 43 | $result = gsudo {Get-Command Get-Help} 44 | $result.CommandType -eq [System.Management.Automation.CommandTypes]::Cmdlet | Should -BeTrue 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/gsudo.Wrappers/gsudo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a wrapper for gsudo intended to be used from WSL. 3 | # Ensures WSLENV is set before calling gsudo, to let gsudo know the WSL context. 4 | # Also allows `gsudo` usage, no need for `gsudo.exe` 5 | # On git-bash/cygwin, by setting MSYS_NO_PATHCONV, it disables path translation so the original bash command can be elevated as-is. 6 | # You can also use 'gsudo.exe [command]' syntax (to avoids this wrapper), 7 | 8 | # For better experience (fix credentials cache) in git-bash/MinGw create this wrapper can be added as function in .bashrc: 9 | # gsudo() { WSLENV=WSL_DISTRO_NAME:USER:$WSLENV MSYS_NO_PATHCONV=1 gsudo.exe "$@"; } 10 | 11 | thisdir="$(dirname "$(readlink -f "$0")")" 12 | WSLENV=WSL_DISTRO_NAME:USER:$WSLENV MSYS_NO_PATHCONV=1 "${thisdir}/gsudo.exe" "$@" 13 | -------------------------------------------------------------------------------- /src/gsudo.Wrappers/gsudoModule.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'gsudoModule' 3 | # 4 | # Generated by: Gerardo Grignoli 5 | # 6 | # Generated on: 2/1/2022 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'gsudoModule.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = "0.1" 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'c50a89c0-75c0-4b08-8dac-473404b73379' 22 | 23 | # Author of this module 24 | Author = 'Gerardo Grignoli' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'gsudo' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) Gerardo Grignoli. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | # Description = '' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = 'gsudo', 'invoke-gsudo', 'Test-IsGsudoCacheAvailable', 'Test-IsProcessElevated', 'Test-IsAdminMember', 'gsudoPrompt' 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | VariablesToExport = 'gsudoVerbose', 'gsudoAutoComplete' 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = @() 82 | 83 | # DSC resources to export from this module 84 | # DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | # Tags = @() 99 | 100 | # A URL to the license for this module. 101 | # LicenseUri = '' 102 | 103 | # A URL to the main website for this project. 104 | # ProjectUri = '' 105 | 106 | # A URL to an icon representing this module. 107 | # IconUri = '' 108 | 109 | # ReleaseNotes of this module 110 | # ReleaseNotes = '' 111 | 112 | # Prerelease string of this module 113 | # Prerelease = '' 114 | 115 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 116 | # RequireLicenseAcceptance = $false 117 | 118 | # External dependent modules of this module 119 | # ExternalModuleDependencies = @() 120 | 121 | } # End of PSData hashtable 122 | 123 | } # End of PrivateData hashtable 124 | 125 | # HelpInfo URI of this module 126 | # HelpInfoURI = '' 127 | 128 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 129 | # DefaultCommandPrefix = '' 130 | 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/gsudo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32421.90 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gsudo", "gsudo\gsudo.csproj", "{9EEBDA90-12DE-4780-B8B1-8BF86DFE71B3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gsudo.Tests", "gsudo.Tests\gsudo.Tests.csproj", "{B91748FC-950F-46AB-A037-7118BEA81FF7}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyPressTester", "KeyPressTester\KeyPressTester.csproj", "{FD3B76FD-E682-4C65-A936-84B9B2C6FB19}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {9EEBDA90-12DE-4780-B8B1-8BF86DFE71B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {9EEBDA90-12DE-4780-B8B1-8BF86DFE71B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {9EEBDA90-12DE-4780-B8B1-8BF86DFE71B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9EEBDA90-12DE-4780-B8B1-8BF86DFE71B3}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B91748FC-950F-46AB-A037-7118BEA81FF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B91748FC-950F-46AB-A037-7118BEA81FF7}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B91748FC-950F-46AB-A037-7118BEA81FF7}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B91748FC-950F-46AB-A037-7118BEA81FF7}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {FD3B76FD-E682-4C65-A936-84B9B2C6FB19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {FD3B76FD-E682-4C65-A936-84B9B2C6FB19}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {FD3B76FD-E682-4C65-A936-84B9B2C6FB19}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {FD3B76FD-E682-4C65-A936-84B9B2C6FB19}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {9B62A41B-CF92-42EA-A268-9E1B01ABEE3B} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/gsudo/AppSettings/PathPrecedenceSetting.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace gsudo.AppSettings 9 | { 10 | /// 11 | /// Reorders the PATH environment variable to prioritize gsudo's path. 12 | /// Saving the boolean value to the registry is anecdotical, the real change is done in the environment variable. 13 | /// 14 | internal class PathPrecedenceSetting : RegistrySetting 15 | { 16 | public PathPrecedenceSetting(): 17 | base("PathPrecedence", false, bool.Parse, RegistrySettingScope.GlobalOnly, 18 | description: "Prioritize gsudo over Microsoft Sudo in the PATH environment variable") 19 | { 20 | 21 | } 22 | 23 | public override void Save(string newValue, bool global) 24 | { 25 | bool bNewValue = bool.Parse(newValue); 26 | var ourPath = Path.GetDirectoryName(ProcessFactory.FindExecutableInPath("gsudo.exe")) // shim 27 | ?? Path.GetDirectoryName(ProcessHelper.GetOwnExeName()); 28 | 29 | var system32Path = Environment.GetFolderPath(Environment.SpecialFolder.System); 30 | 31 | var allPaths = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); 32 | // I could also do .Distinct(StringComparer.OrdinalIgnoreCase); 33 | // ...and it works well on local, but may be out of our responsibility to fix that. 34 | 35 | IEnumerable newPath; 36 | 37 | if (bNewValue) 38 | newPath = new[] { ourPath }.Concat(allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase))); 39 | else 40 | newPath = allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase)).Concat(new[] { ourPath }); 41 | 42 | var finalStringPath = string.Join(";", newPath); 43 | 44 | Logger.Instance.Log($"Updating PATH environment variable to: {finalStringPath}", LogLevel.Debug); 45 | 46 | Environment.SetEnvironmentVariable("Path", finalStringPath, EnvironmentVariableTarget.Machine); 47 | base.Save(newValue, global); 48 | 49 | if (bNewValue) 50 | Logger.Instance.Log($"\"{ourPath}\" path is now prioritized in the PATH environment variable.", LogLevel.Info); 51 | else 52 | Logger.Instance.Log($"\"{system32Path}\" path is now prioritized in the PATH environment variable.", LogLevel.Info); 53 | 54 | Logger.Instance.Log("Please restart all your consoles to ensure the change makes effect.", LogLevel.Warning); 55 | 56 | // Notify the system of the change 57 | SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, "Environment"); 58 | } 59 | 60 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 61 | private static extern bool SendNotifyMessage( 62 | IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam); 63 | 64 | private const uint WM_SETTINGCHANGE = 0x001A; 65 | private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/gsudo/AppSettings/Window.cs: -------------------------------------------------------------------------------- 1 | namespace gsudo.AppSettings 2 | { 3 | // CacheMode = Disabled | Explicit | Auto 4 | // Cache.Mode = Disabled | Manual | AutoStart | AutoStart 5 | // Cache.Restriction = CallerPIDOnly | CallerPIDAndDescendants | AnySameUserPid 6 | 7 | // NewWindow.Force = true 8 | // NewWindow.CloseBehaviour = KeepShellOpen | PressKeyToClose | OsDefault 9 | 10 | public enum CloseBehaviour 11 | { 12 | KeepShellOpen, 13 | PressKeyToClose, 14 | OsDefault, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gsudo/Commands/BangBangCommand.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Threading.Tasks; 8 | 9 | namespace gsudo.Commands 10 | { 11 | class BangBangCommand : ICommand 12 | { 13 | public string Pattern { get; internal set; } 14 | 15 | public Task Execute() 16 | { 17 | if (ShellHelper.InvokingShell.In (Shell.PowerShell, Shell.PowerShellCore, Shell.PowerShellCore623BuggedGlobalInstall)) 18 | { 19 | throw new ApplicationException($"To use `gsudo !!` from PowerShell, run or add the following line to your PowerShell $PROFILE:\n\n Import-Module 'gsudoModule'"); 20 | } 21 | 22 | var caller = Process.GetCurrentProcess().GetShellProcess().MainModule.ModuleName; 23 | var length = (int)NativeMethods.GetConsoleCommandHistoryLength(caller); 24 | 25 | if (length == 0) 26 | throw new ApplicationException("Failed to find last invoked command (GetConsoleCommandHistoryLength==0)"); 27 | 28 | IntPtr CommandBuffer = Marshal.AllocHGlobal(length); 29 | var ret = NativeMethods.GetConsoleCommandHistory(CommandBuffer, length, caller); 30 | 31 | if (ret == 0) 32 | throw new ApplicationException($"Failed to find last invoked command (GetConsoleCommandHistory=0; LastErr={Marshal.GetLastWin32Error()})"); 33 | 34 | string commandToElevate; 35 | 36 | var commandHistory = Marshal.PtrToStringAuto(CommandBuffer, length / 2) 37 | .Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries) 38 | .Reverse() // look for last commands first 39 | .Skip(1) // skip gsudo call 40 | ; 41 | 42 | if (Pattern == "!!") 43 | { 44 | commandToElevate = commandHistory.FirstOrDefault(); 45 | } 46 | else if (Pattern.StartsWith("!?", StringComparison.OrdinalIgnoreCase)) 47 | { 48 | commandToElevate = commandHistory.FirstOrDefault(s => s.ToUpperInvariant().Contains(Pattern.Substring(2).Trim().ToUpperInvariant())); 49 | } 50 | else // Pattern.StartsWith ("!command") 51 | { 52 | commandToElevate = commandHistory.FirstOrDefault(s => s.StartsWith(Pattern.Substring(1).Trim(), StringComparison.OrdinalIgnoreCase)); 53 | } 54 | 55 | if (commandToElevate == null) 56 | throw new ApplicationException("Failed to find last invoked command in history."); 57 | 58 | Logger.Instance.Log("Command to run: " + commandToElevate, LogLevel.Info); 59 | 60 | return new RunCommand(commandToRun: ArgumentsHelper.SplitArgs(commandToElevate)) 61 | .Execute(); 62 | } 63 | 64 | class NativeMethods 65 | { 66 | // Many thanks to comment from eryk sun for posting here: https://www.hanselman.com/blog/ForgottenButAwesomeWindowsCommandPromptFeatures.aspx 67 | // (Otherwise I wouldnt be able to find this undocumented api.) 68 | 69 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 70 | public static extern UInt32 GetConsoleCommandHistoryLength(string ExeName); 71 | 72 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 73 | public static extern UInt32 GetConsoleCommandHistory( 74 | IntPtr CommandBuffer, 75 | int CommandBufferLength, 76 | string ExeName); 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/gsudo/Commands/CtrlCCommand.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using System; 3 | using System.Globalization; 4 | using System.Threading.Tasks; 5 | using static gsudo.Native.ConsoleApi; 6 | 7 | namespace gsudo.Commands 8 | { 9 | // Required for sending Ctrl-C / Ctrl-Break to the elevated process on VT & piped Mode. 10 | class CtrlCCommand : ICommand 11 | { 12 | public int Pid { get; set; } 13 | 14 | public bool SendSigBreak { get; set; } 15 | 16 | public Task Execute() 17 | { 18 | FreeConsole(); 19 | 20 | if (AttachConsole(Pid)) 21 | { 22 | if (SendSigBreak) 23 | GenerateConsoleCtrlEvent(CtrlTypes.CTRL_BREAK_EVENT, 0); 24 | else 25 | GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0); 26 | 27 | FreeConsole(); 28 | } 29 | else 30 | { 31 | return Task.FromResult(Constants.GSUDO_ERROR_EXITCODE); 32 | } 33 | 34 | return Task.FromResult(0); 35 | } 36 | 37 | public static void Invoke(int procId, bool sendSigBreak = false) 38 | { 39 | // Sending Ctrl-C in windows is tricky. 40 | // Your process must be attached to the target process console. 41 | // There is no way to do that without loosing your currently attached console, and that generates a lot of issues. 42 | // So the best we can do is create a new process that will attach and send Ctrl-C to the target process. 43 | 44 | using (var p = ProcessFactory.StartDetached 45 | (ProcessHelper.GetOwnExeName(), $"gsudoctrlc {procId.ToString(CultureInfo.InvariantCulture)} {sendSigBreak}", Environment.CurrentDirectory, true)) 46 | { 47 | p.WaitForExit(); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/gsudo/Commands/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Text.RegularExpressions; 4 | using System.Threading.Tasks; 5 | 6 | namespace gsudo.Commands 7 | { 8 | public class HelpCommand : ICommand 9 | { 10 | public virtual Task Execute() 11 | { 12 | ShowHelp(); 13 | return Task.FromResult(0); 14 | } 15 | 16 | internal static void ShowVersion(bool verbose = true) 17 | { 18 | var assembly = Assembly.GetExecutingAssembly(); 19 | Console.ForegroundColor = ConsoleColor.Yellow; 20 | Console.WriteLine($"{assembly.GetName().Name} v{GitVersionInformation.FullSemVer} ({GitVersionInformation.FullBuildMetaData})"); 21 | 22 | Console.ResetColor(); 23 | if (verbose) Console.WriteLine("Copyright(c) 2019-2022 Gerardo Grignoli and GitHub contributors"); 24 | } 25 | 26 | internal static void ShowHelp() 27 | { 28 | ShowVersion(false); 29 | 30 | /* 31 | -h | --help\t\tShows this help 32 | -v | --version\t\tShows gsudo version 33 | */ 34 | 35 | Console.WriteLine(@" 36 | Usage: 37 | gsudo [options]\t\t\tElevates your current shell 38 | gsudo [options] {command} [args]\tRuns {command} with elevated permissions 39 | gsudo cache [on | off | help] \t\tStarts/Stops an elevated cache session. (reduced UAC popups) 40 | gsudo status [--json]\t\t\tShows current user, cache and console status. 41 | gsudo status {key} [--no-output]\tShows status filtered by json {key}. Boolean keys also returned as exit codes. 42 | gsudo !!\t\t\t\tRe-run last command as admin. (YMMV) 43 | 44 | New Window options: 45 | -n | --new Starts the command in a new console (and returns immediately). 46 | -w | --wait When in new console, wait for the command to end and return the exitcode. 47 | --keepShell Keep elevated shell open after running {command}. 48 | --keepWindow When in new console, ask for keypress before closing the console. 49 | --close Override settings and always close new window at end. 50 | 51 | Security options: 52 | -i | --integrity {v} Run with integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System 53 | -u | --user {username} Run as the specified user. Asks for password. For local admins shows UAC unless '-i Medium'. 54 | -s | --system Run as Local System account (NT AUTHORITY\SYSTEM). 55 | --ti Run as member of NT SERVICE\TrustedInstaller group. 56 | -k | --reset-timestamp Kills all cached credentials. The next time gsudo is run a UAC popup will be appear. 57 | 58 | Shell related options: 59 | -d | --direct Skip Shell detection. Assume CMD shell or CMD {command}. 60 | 61 | Other options: 62 | --loglevel {val} Set minimum log level to display: All, Debug, Info, Warning, Error, None. 63 | --debug Enable debug mode. 64 | --copyns Connect network drives to the elevated user. Warning: Interactive asks for credentials. 65 | --copyev (deprecated) Copy all environment variables to the elevated process. 66 | --chdir {dir} Change the current directory to {dir} before running the command. 67 | 68 | Configuration: 69 | gsudo config\t\t\t\tShow current configuration settings & values. 70 | gsudo config {key} [--global] [value] \tRead or write a configuration setting. 71 | gsudo config {key} [--global] --reset \tReset a specific setting to its default value. 72 | gsudo config --reset-all \t\tReset all user and global settings to their default values. 73 | --global\t\t\t\tApplies to all users (overrides user-specific settings) 74 | 75 | (Note: User settings are stored at HKCU\Software\gsudo and globals at HKLM\Software\gsudo) 76 | 77 | Usage from PowerShell: 78 | gsudo [options] [--loadProfile] { ScriptBlock } [-args $argument1 [..., $argumentN]] 79 | { ScriptBlock }\t\tMust be wrapped in { curly brackets } 80 | --loadProfile\t\tWhen elevating PowerShell commands, load user profile. 81 | 82 | Example: gsudo { Write-Output ""Hello World"" } -args 83 | Tip: Add `Import-Module gsudoModule` to your $PROFILE for tab auto-complete. 84 | 85 | Learn more about security considerations of using gsudo at: https://gerardog.github.io/gsudo/docs/security 86 | " 87 | 88 | .ReplaceOrdinal("\\t", "\t")); 89 | return; 90 | } 91 | } 92 | 93 | class ShowVersionHelpCommand : HelpCommand 94 | { 95 | public override Task Execute() 96 | { 97 | ShowVersion(); 98 | return Task.FromResult(0); 99 | } 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/gsudo/Commands/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace gsudo.Commands 4 | { 5 | public interface ICommand 6 | { 7 | Task Execute(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/gsudo/Commands/KillCacheCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using gsudo.CredentialsCache; 4 | 5 | namespace gsudo.Commands 6 | { 7 | /// 8 | /// Command that signals 'kill-cache' to all gsudo services. 9 | /// 10 | public class KillCacheCommand : ICommand 11 | { 12 | public bool Verbose { get; set; } 13 | 14 | public KillCacheCommand() 15 | { 16 | Verbose = true; 17 | } 18 | 19 | public KillCacheCommand(bool verbose) 20 | { 21 | Verbose = verbose; 22 | } 23 | 24 | public Task Execute() 25 | { 26 | try 27 | { 28 | if (CredentialsCacheLifetimeManager.ClearCredentialsCache()) 29 | { 30 | if (Verbose) 31 | Logger.Instance.Log("All credentials cache were invalidated.", LogLevel.Info); 32 | } 33 | else 34 | { 35 | if (Verbose) 36 | Logger.Instance.Log("No active credentials found to invalidate.", LogLevel.Info); 37 | } 38 | 39 | return Task.FromResult(0); 40 | } 41 | catch (Exception ex) 42 | { 43 | throw new ApplicationException($"Failed to invalidate Credentials Cache: {ex.ToString()}"); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/gsudo/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace gsudo 2 | { 3 | class Constants 4 | { 5 | // All tokens must have small amount of chars, to avoid the token being split by the network chunking 6 | internal const string TOKEN_FOCUS = "\u0011"; 7 | internal const string TOKEN_EXITCODE = "\u0012"; 8 | internal const string TOKEN_ERROR = "\u0013"; 9 | internal const string TOKEN_KEY_CTRLC = "\u0014"; 10 | internal const string TOKEN_KEY_CTRLBREAK = "\u0015"; 11 | internal const string TOKEN_SUCCESS = "\u0016"; 12 | internal const string TOKEN_EOF = "\u0017"; 13 | internal const int GSUDO_ERROR_EXITCODE = 999; 14 | internal const string TI_SID = "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gsudo/CredentialsCache/CacheMode.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace gsudo.CredentialsCache 4 | { 5 | enum CacheMode 6 | { 7 | Disabled, 8 | Explicit, 9 | [Description("Enabling the credentials cache is a security risk.")] 10 | Auto, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gsudo/ElevationRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace gsudo 5 | { 6 | [Serializable] 7 | class ElevationRequest 8 | { 9 | public string Prompt { get; set; } 10 | public string FileName { get; set; } 11 | public string Arguments { get; set; } 12 | public string StartFolder { get; set; } 13 | public bool NewWindow { get; set; } 14 | public bool Wait { get; set; } 15 | public int ConsoleWidth { get; set; } 16 | public int ConsoleHeight { get; set; } 17 | public ConsoleMode Mode { get; set; } 18 | public int ConsoleProcessId { get; set; } 19 | public int TargetProcessId { get; set; } 20 | public bool KillCache { get; set; } 21 | public IntegrityLevel IntegrityLevel { get; set; } 22 | 23 | public bool IsInputRedirected { get; set; } 24 | public bool DisableInput { get; set; } 25 | 26 | [Serializable] 27 | internal enum ConsoleMode { 28 | /// 29 | /// Process started at the service, I/O streamed via named pipes. 30 | /// 31 | Piped, 32 | /// 33 | /// Process started at the service using PseudoConsole, VT100 I/O streamed via named pipes. 34 | /// 35 | VT, 36 | /// 37 | /// Process started at the service, then attached to the caller console unsing APIs. 38 | /// 39 | Attached, 40 | /// 41 | /// Process started at the client, then the service replaces it's security token. 42 | /// 43 | TokenSwitch 44 | } 45 | } 46 | 47 | #if NETFRAMEWORK 48 | class MySerializationBinder : SerializationBinder 49 | { 50 | /// 51 | /// look up the type locally if the assembly-name is "NA" 52 | /// 53 | /// 54 | /// 55 | /// 56 | public override Type BindToType(string assemblyName, string typeName) 57 | { 58 | return Type.GetType(typeName); 59 | } 60 | 61 | /// 62 | /// override BindToName in order to strip the assembly name. Setting assembly name to null does nothing. 63 | /// 64 | /// 65 | /// 66 | /// 67 | public override void BindToName(Type serializedType, out string assemblyName, out string typeName) 68 | { 69 | assemblyName = serializedType.Assembly.FullName; 70 | typeName = serializedType.FullName; 71 | } 72 | } 73 | #else 74 | [System.Text.Json.Serialization.JsonSerializable(typeof(ElevationRequest))] 75 | internal partial class ElevationRequestJsonContext : System.Text.Json.Serialization.JsonSerializerContext 76 | { 77 | } 78 | #endif 79 | } 80 | -------------------------------------------------------------------------------- /src/gsudo/Helpers/ArgumentsHelper.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Native; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace gsudo.Helpers 7 | { 8 | public static class ArgumentsHelper 9 | { 10 | /// 11 | /// Splits arguments. Quoted segments remain joined and quotes preserved. 12 | /// 13 | /// 14 | /// SplitArgs("\"my exe name\" \"my params\" OtherParam1") => new string[] { "\"my exe name\"", "\"my params\"", "OtherParam1"}; 15 | /// 16 | public static IList SplitArgs(string args) 17 | { 18 | args = args.Trim(); 19 | var results = new List(); 20 | int pushed = 0; 21 | int curr = 0; 22 | bool insideQuotes = false; 23 | while (curr < args.Length) 24 | { 25 | if (args[curr] == '"') 26 | insideQuotes = !insideQuotes; 27 | else if (args[curr] == ' ' && !insideQuotes) 28 | { 29 | if ((curr - pushed) > 0) 30 | results.Add(args.Substring(pushed, curr - pushed)); 31 | pushed = curr + 1; 32 | } 33 | curr++; 34 | } 35 | 36 | if (pushed < curr) 37 | results.Add(args.Substring(pushed, curr - pushed)); 38 | return results; 39 | } 40 | 41 | internal static string GetRealCommandLine() 42 | { 43 | System.IntPtr ptr = ConsoleApi.GetCommandLine(); 44 | string commandLine = Marshal.PtrToStringAuto(ptr).TrimStart(); 45 | 46 | if (commandLine[0] == '"') 47 | return commandLine.Substring(commandLine.IndexOf('"', 1) + 1).TrimStart(' '); 48 | else if (commandLine.IndexOf(' ', 1) >= 0) 49 | return commandLine.Substring(commandLine.IndexOf(' ', 1) + 1).TrimStart(' '); 50 | else 51 | return string.Empty; 52 | } 53 | 54 | public static string UnQuote(this string v) 55 | { 56 | if (string.IsNullOrEmpty(v)) 57 | return v; 58 | if (v[0] == '"' && v[v.Length - 1] == '"') 59 | return v.Substring(1, v.Length - 2); 60 | if (v[0] == '"' && v.Trim().EndsWith("\"", StringComparison.Ordinal)) 61 | return UnQuote(v.Trim()); 62 | if (v[0] == '"') 63 | return v.Substring(1); 64 | else 65 | return v; 66 | } 67 | 68 | public static string Quote(this string v) 69 | { 70 | return $"\"{v}\""; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/gsudo/Helpers/LoginHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Principal; 3 | 4 | namespace gsudo.Helpers 5 | { 6 | internal static class LoginHelper 7 | { 8 | internal static string ValidateUserName(string userName) 9 | { 10 | try 11 | { 12 | return new NTAccount(userName).Translate(typeof(SecurityIdentifier)).Translate(typeof(NTAccount)).Value; 13 | } 14 | catch (Exception ex) 15 | { 16 | throw new ApplicationException($"Value \"{userName}\" is not a valid Username.", ex ); 17 | } 18 | } 19 | 20 | internal static string GetSidFromUserName(string userName) 21 | { 22 | try 23 | { 24 | return new NTAccount(userName).Translate(typeof(SecurityIdentifier)).Value; 25 | } 26 | catch (Exception ex) 27 | { 28 | throw new ApplicationException($"Value \"{userName}\" is not a valid Username.", ex); 29 | } 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/gsudo/Helpers/SecurityHelper.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Native; 2 | using System; 3 | using System.Linq; 4 | using System.Security.Principal; 5 | 6 | namespace gsudo.Helpers 7 | { 8 | internal static class SecurityHelper 9 | { 10 | public static bool IsMemberOfLocalAdmins() 11 | { 12 | var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); 13 | var claims = principal.Claims; 14 | return claims.Any(c => c.Value == "S-1-5-32-544"); 15 | } 16 | 17 | private static int? _cacheGetCurrentIntegrityLevelCache; 18 | public static bool IsHighIntegrity() 19 | { 20 | return GetCurrentIntegrityLevel() >= (int)IntegrityLevel.High; 21 | } 22 | 23 | /// 24 | /// The function gets the integrity level of the current process. 25 | /// 26 | /// 27 | /// Returns the integrity level of the current process. It is usually one of 28 | /// these values: 29 | /// 30 | /// SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level 31 | /// SECURITY_MANDATORY_LOW_RID - means low integrity level. 32 | /// SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level. 33 | /// SECURITY_MANDATORY_HIGH_RID - means high integrity level. 34 | /// SECURITY_MANDATORY_SYSTEM_RID - means system integrity level. 35 | /// 36 | /// 37 | /// 38 | /// When any native Windows API call fails, the function throws a Win32Exception 39 | /// with the last error code. 40 | /// 41 | static internal int GetCurrentIntegrityLevel() 42 | { 43 | if (_cacheGetCurrentIntegrityLevelCache.HasValue) return _cacheGetCurrentIntegrityLevelCache.Value; 44 | _cacheGetCurrentIntegrityLevelCache = ProcessHelper.GetProcessIntegrityLevel(ProcessApi.GetCurrentProcess()); 45 | 46 | return _cacheGetCurrentIntegrityLevelCache.Value; 47 | } 48 | 49 | private static bool? _cacheIsAdmin; 50 | public static bool IsAdministrator() 51 | { 52 | if (_cacheIsAdmin.HasValue) return _cacheIsAdmin.Value; 53 | 54 | try 55 | { 56 | WindowsIdentity identity = WindowsIdentity.GetCurrent(); 57 | WindowsPrincipal principal = new WindowsPrincipal(identity); 58 | _cacheIsAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); 59 | return _cacheIsAdmin.Value; 60 | } 61 | catch (Exception) 62 | { 63 | return false; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/gsudo/Helpers/StringTokenizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace gsudo.Helpers 8 | { 9 | class StringTokenizer 10 | { 11 | public static IEnumerable Split(string input, string[] separators) 12 | { 13 | int lastPushed = 0; 14 | List results = new List(); 15 | 16 | for (int i = 0; i < input.Length /*&& foundCount < sepListCount*/; i++) 17 | { 18 | for (int j = 0; j < separators.Length; j++) 19 | { 20 | String separator = separators[j]; 21 | if (String.IsNullOrEmpty(separator)) continue; 22 | 23 | Int32 currentSepLength = separator.Length; 24 | if (input[i] == separator[0] && currentSepLength <= input.Length - i) 25 | { 26 | if (currentSepLength == 1 27 | || String.CompareOrdinal(input, i, separator, 0, currentSepLength) == 0) 28 | { 29 | if (i - lastPushed>0) 30 | yield return input.Substring(lastPushed, i - lastPushed); 31 | 32 | yield return input.Substring(i, currentSepLength); 33 | i += currentSepLength - 1; 34 | lastPushed = i+1; 35 | break; 36 | } 37 | } 38 | } 39 | } 40 | if (input.Length > lastPushed) 41 | yield return input.Substring(lastPushed, input.Length - lastPushed); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/gsudo/Helpers/SymbolicLinkSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | 9 | namespace gsudo.Helpers 10 | { 11 | static class SymbolicLinkSupport 12 | { 13 | static string RealRoot; 14 | 15 | /// 16 | /// Enables this application to be called from a SymLink without assembly load failures. 17 | /// 18 | public static void EnableAssemblyLoadFix() 19 | { 20 | #if NETFRAMEWORK 21 | string exeName = ProcessHelper.GetOwnExeName(); 22 | string exeNamePath = Path.GetDirectoryName(exeName); 23 | 24 | RealRoot = Path.GetDirectoryName(ResolveSymbolicLink(exeName)); 25 | 26 | if (!string.IsNullOrEmpty(RealRoot) && RealRoot != exeNamePath) 27 | { 28 | AppDomain.CurrentDomain.SetData("APPBASE", RealRoot); // I don't know if this line has any effect at all. Feel free to delete it if you have good reason. 29 | AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 30 | } 31 | } 32 | 33 | private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 34 | { 35 | var target = Path.Combine(RealRoot, args.Name.Split(',')[0] + ".dll"); 36 | if (File.Exists(target)) 37 | return Assembly.LoadFrom(target); 38 | return null; 39 | #endif 40 | } 41 | 42 | public static string ResolveSymbolicLink(string symLinkFullPath) 43 | { 44 | return GetFinalPathName(symLinkFullPath) 45 | .ReplaceOrdinal("\\\\?\\UNC\\", "\\\\") 46 | .ReplaceOrdinal("\\\\?\\", "") 47 | ; 48 | } 49 | private static string GetFinalPathName(string path) 50 | { 51 | var h = Native.FileApi.CreateFile(path, 52 | Native.FileApi.FILE_READ_EA, 53 | FileShare.ReadWrite | FileShare.Delete, 54 | IntPtr.Zero, 55 | FileMode.Open, 56 | Native.FileApi.FILE_FLAG_BACKUP_SEMANTICS, 57 | IntPtr.Zero); 58 | 59 | if (h == Native.FileApi.INVALID_HANDLE_VALUE) 60 | return path; 61 | 62 | try 63 | { 64 | var sb = new StringBuilder(1024); 65 | var res = Native.FileApi.GetFinalPathNameByHandle(h, sb, 1024, 0); 66 | 67 | if (res == 0) 68 | { 69 | Logger.Instance.Log($"{nameof(SymbolicLinkSupport)}.{nameof(GetFinalPathName)} failed with: {new Win32Exception()}", LogLevel.Debug); 70 | return path; // Sad workaround: do not resolve the symlink. 71 | } 72 | 73 | return sb.ToString(); 74 | } 75 | finally 76 | { 77 | Native.FileApi.CloseHandle(h); 78 | } 79 | } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/gsudo/Helpers/UACWindowFocusHelper.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Native; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | 6 | namespace gsudo.Helpers 7 | { 8 | internal class UACWindowFocusHelper 9 | { 10 | [DllImport("user32.dll", SetLastError = true)] 11 | private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 12 | 13 | internal static void StartBackgroundThreadToFocusUacWindow() 14 | { 15 | var focusThread = new Thread(UACWindowFocusHelper.FocusUacWindow); 16 | focusThread.IsBackground = true; 17 | focusThread.Start(); 18 | } 19 | 20 | internal static void FocusUacWindow() 21 | { 22 | try 23 | { 24 | for (int i = 0; i < 10; i++) 25 | { 26 | // Wait a moment to allow the UAC prompt to appear 27 | System.Threading.Thread.Sleep(100); 28 | 29 | // Find the UAC window 30 | string classname = "Credential Dialog Xaml Host"; // Found using Visual Studio spyxx_amd64.exe, this is the value for Windows 10 & 11. 31 | IntPtr uacWindow = FindWindow(classname, null); 32 | if (uacWindow != IntPtr.Zero) 33 | { 34 | // Set focus to the UAC window 35 | WindowApi.SetForegroundWindow(uacWindow); 36 | return; 37 | } 38 | } 39 | } 40 | catch (Exception ex) 41 | { 42 | Logger.Instance.Log("Error searching for UAC Window: " + ex.ToString(), LogLevel.Debug); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/gsudo/InputParameters.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using gsudo.AppSettings; 3 | 4 | namespace gsudo 5 | { 6 | public static class InputArguments 7 | { 8 | // Show debug info 9 | public static bool Debug { get; internal set; } 10 | 11 | // Open in new window 12 | public static bool NewWindow { get; internal set; } 13 | 14 | // When elevating a command, keep the elevated shell open afterwards. 15 | public static bool KeepShellOpen { get; internal set; } 16 | public static bool KeepWindowOpen { get; internal set; } 17 | public static bool CloseNewWindow { get; internal set; } 18 | 19 | // Wait for new process to end 20 | public static bool Wait { get; internal set; } 21 | 22 | // In `gsudo --global config Key Value` --global means save as machine setting. 23 | public static bool Global { get; internal set; } 24 | 25 | // Kill credentials cache after running. 26 | public static bool KillCache { get; internal set; } 27 | 28 | // Skip shell detection and asume called from CMD. 29 | public static bool Direct { get; internal set; } 30 | 31 | // Target Integrity Level 32 | public static IntegrityLevel? IntegrityLevel { get; internal set; } 33 | 34 | // Elevate as "NT Authority\System" 35 | public static bool RunAsSystem { get; internal set; } 36 | 37 | // Elevate as "NT Authority\System" but member of "NT SERVICE\TrustedInstaller" group (run whoami /groups) 38 | public static bool TrustedInstaller { get; internal set; } 39 | 40 | // User to Impersonate 41 | public static string UserName { get; private set; } 42 | // SID of User to Impersonate 43 | public static string UserSid { get; private set; } 44 | 45 | // Starting Directory for the new process 46 | public static string StartingDirectory { get; internal set; } 47 | public static bool DisableInput { get; internal set; } 48 | 49 | public static IntegrityLevel GetIntegrityLevel() => (RunAsSystem ? gsudo.IntegrityLevel.System : IntegrityLevel ?? gsudo.IntegrityLevel.High); 50 | 51 | internal static void Clear() // added for tests repeatability 52 | { 53 | Debug = false; 54 | NewWindow = false; 55 | Wait = false; 56 | RunAsSystem = false; 57 | Global = false; 58 | KillCache = false; 59 | Direct = false; 60 | TrustedInstaller = false; 61 | IntegrityLevel = null; 62 | UserName = null; 63 | UserSid = null; 64 | } 65 | 66 | internal static void SetUserName(string username) 67 | { 68 | UserName = LoginHelper.ValidateUserName(username); 69 | UserSid = LoginHelper.GetSidFromUserName(UserName); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/gsudo/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace gsudo 4 | { 5 | public enum LogLevel 6 | { 7 | All = 0, 8 | Debug = 1, 9 | Info = 2, 10 | Warning = 3, 11 | Error = 4, 12 | None = 5, 13 | } 14 | 15 | class Logger 16 | { 17 | public static readonly Logger Instance = new Logger(); 18 | 19 | private Logger() { } 20 | 21 | public void Log(string message, LogLevel level) 22 | { 23 | try 24 | { 25 | if (level >= Settings.LogLevel) 26 | { 27 | Console.ForegroundColor = GetColor(level); 28 | Console.Error.WriteLine($"{level.ToString()}: {message}"); 29 | Console.ResetColor(); 30 | } 31 | } 32 | catch { } 33 | } 34 | 35 | private static ConsoleColor GetColor(LogLevel level) 36 | { 37 | if (level <= LogLevel.Debug) return ConsoleColor.DarkGray; 38 | if (level == LogLevel.Info) return ConsoleColor.Gray; 39 | if (level == LogLevel.Warning) return ConsoleColor.Yellow; 40 | return ConsoleColor.Red; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/gsudo/Native/FileApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace gsudo.Native 7 | { 8 | static class FileApi 9 | { 10 | internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); 11 | 12 | internal const uint FILE_READ_EA = 0x0008; 13 | internal const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000; 14 | 15 | public const uint GENERIC_READ = (0x80000000); 16 | public const uint GENERIC_WRITE = (0x40000000); 17 | 18 | [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 19 | internal static extern uint GetFinalPathNameByHandle(IntPtr hFile, StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); 20 | 21 | [DllImport("kernel32.dll", SetLastError = true)] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | internal static extern bool CloseHandle(IntPtr hObject); 24 | 25 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 26 | internal static extern IntPtr CreateFile( 27 | [MarshalAs(UnmanagedType.LPWStr)] string filename, 28 | [MarshalAs(UnmanagedType.U4)] uint access, 29 | [MarshalAs(UnmanagedType.U4)] FileShare share, 30 | IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero 31 | [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, 32 | [MarshalAs(UnmanagedType.U4)] uint flagsAndAttributes, 33 | IntPtr templateFile); 34 | 35 | #region IsWindowsApp Win32 Api 36 | [DllImport("shell32.dll", CharSet = CharSet.Unicode)] 37 | internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); 38 | 39 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 40 | internal struct SHFILEINFO 41 | { 42 | public IntPtr hIcon; 43 | public int iIcon; 44 | public uint dwAttributes; 45 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 46 | public string szDisplayName; 47 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] 48 | public string szTypeName; 49 | } 50 | #endregion 51 | 52 | #region Named Pipes File Access 53 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 54 | public struct WIN32_FIND_DATA 55 | { 56 | public uint dwFileAttributes; 57 | public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; 58 | public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; 59 | public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; 60 | public uint nFileSizeHigh; 61 | public uint nFileSizeLow; 62 | public uint dwReserved0; 63 | public uint dwReserved1; 64 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 65 | public string cFileName; 66 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] 67 | public string cAlternateFileName; 68 | } 69 | 70 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 71 | public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); 72 | 73 | 74 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 75 | public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA 76 | lpFindFileData); 77 | 78 | [DllImport("kernel32.dll", SetLastError = true)] 79 | public static extern bool FindClose(IntPtr hFindFile); 80 | 81 | [DllImport("shlwapi", EntryPoint = "PathFileExists", CharSet = CharSet.Unicode)] 82 | public static extern bool PathExists(string path); 83 | 84 | #endregion 85 | 86 | #region Network Drives 87 | [DllImport("mpr.dll", CharSet = CharSet.Unicode)] 88 | public static extern uint WNetGetConnection(string lpLocalName, StringBuilder lpRemoteName, ref int lpnLength); 89 | #endregion 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/gsudo/Native/NtDllApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace gsudo.Native 5 | { 6 | internal static class NtDllApi 7 | { 8 | internal class NativeMethods 9 | { 10 | [DllImport("ntdll.dll", SetLastError = true)] 11 | public static extern int NtSetInformationProcess(IntPtr hProcess, PROCESS_INFORMATION_CLASS processInformationClass, ref PROCESS_ACCESS_TOKEN processInformation, int processInformationLength); 12 | 13 | [DllImport("ntdll.dll", SetLastError = true)] 14 | public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref PROCESS_BASIC_INFORMATION processInformation, int processInformationLength, out int returnLength); 15 | } 16 | 17 | public enum PROCESS_INFORMATION_CLASS 18 | { 19 | ProcessBasicInformation, 20 | ProcessQuotaLimits, 21 | ProcessIoCounters, 22 | ProcessVmCounters, 23 | ProcessTimes, 24 | ProcessBasePriority, 25 | ProcessRaisePriority, 26 | ProcessDebugPort, 27 | ProcessExceptionPort, 28 | ProcessAccessToken, 29 | ProcessLdtInformation, 30 | ProcessLdtSize, 31 | ProcessDefaultHardErrorMode, 32 | ProcessIoPortHandlers, 33 | ProcessPooledUsageAndLimits, 34 | ProcessWorkingSetWatch, 35 | ProcessUserModeIOPL, 36 | ProcessEnableAlignmentFaultFixup, 37 | ProcessPriorityClass, 38 | ProcessWx86Information, 39 | ProcessHandleCount, 40 | ProcessAffinityMask, 41 | ProcessPriorityBoost, 42 | MaxProcessInfoClass 43 | } 44 | 45 | internal struct PROCESS_BASIC_INFORMATION 46 | { 47 | public uint /*NtStatus*/ ExitStatus; 48 | public IntPtr PebBaseAddress; 49 | public UIntPtr AffinityMask; 50 | public int BasePriority; 51 | public UIntPtr UniqueProcessId; 52 | public UIntPtr InheritedFromUniqueProcessId; 53 | } 54 | 55 | 56 | [StructLayout(LayoutKind.Sequential)] 57 | internal struct PROCESS_ACCESS_TOKEN 58 | { 59 | public IntPtr Token; 60 | public IntPtr Thread; 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/gsudo/Native/PseudoConsoleApi.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace gsudo.Native 6 | { 7 | /// 8 | /// PInvoke signatures for win32 pseudo console api 9 | /// 10 | static class PseudoConsoleApi 11 | { 12 | internal const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016; 13 | internal const uint PSEUDOCONSOLE_INHERIT_CURSOR = 0x00000001; 14 | 15 | [StructLayout(LayoutKind.Sequential)] 16 | internal struct COORD 17 | { 18 | public short X; 19 | public short Y; 20 | } 21 | 22 | [DllImport("kernel32.dll", SetLastError = true)] 23 | internal static extern int CreatePseudoConsole(COORD size, SafeFileHandle hInput, SafeFileHandle hOutput, uint dwFlags, out IntPtr phPC); 24 | 25 | [DllImport("kernel32.dll", SetLastError = true)] 26 | internal static extern int ResizePseudoConsole(IntPtr hPC, COORD size); 27 | 28 | [DllImport("kernel32.dll", SetLastError = true)] 29 | internal static extern int ClosePseudoConsole(IntPtr hPC); 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/gsudo/Native/SafeTokenHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | 4 | namespace gsudo.Native 5 | { 6 | internal class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid 7 | { 8 | internal SafeTokenHandle(IntPtr handle) 9 | : base(true) 10 | { 11 | base.SetHandle(handle); 12 | } 13 | 14 | private SafeTokenHandle() 15 | : base(true) 16 | { 17 | } 18 | 19 | protected override bool ReleaseHandle() 20 | { 21 | return Native.ProcessApi.CloseHandle(base.handle); 22 | } 23 | } 24 | 25 | internal sealed class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid 26 | { 27 | internal SafeThreadHandle(IntPtr handle) 28 | : base(true) 29 | { 30 | base.SetHandle(handle); 31 | } 32 | 33 | override protected bool ReleaseHandle() 34 | { 35 | return Native.ProcessApi.CloseHandle(handle); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/gsudo/Native/WindowApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace gsudo.Native 4 | { 5 | static class WindowApi 6 | { 7 | [System.Runtime.InteropServices.DllImport("user32.dll")] 8 | public static extern int SetForegroundWindow(IntPtr hwnd); 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/gsudo/ProcessHosts/AttachedConsoleHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Principal; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using gsudo.Helpers; 6 | using gsudo.Rpc; 7 | using static gsudo.Native.ConsoleApi; 8 | 9 | namespace gsudo.ProcessHosts 10 | { 11 | /// 12 | /// Hosts a process that uses Win32 'AttachConsole' Api so its i/o is natively attached to the 13 | /// client console. 14 | /// 15 | // This mode is not enabled unless you use --attached. 16 | class AttachedConsoleHost : IProcessHost 17 | { 18 | public bool SupportsSimultaneousElevations { get; } = false; 19 | 20 | public async Task Start(Connection connection, ElevationRequest elevationRequest) 21 | { 22 | var exitCode = 0; 23 | 24 | try 25 | { 26 | Native.ConsoleApi.FreeConsole(); 27 | int pid = elevationRequest.ConsoleProcessId; 28 | if (Native.ConsoleApi.AttachConsole(pid)) 29 | { 30 | Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true); 31 | 32 | try 33 | { 34 | try 35 | { 36 | System.Environment.CurrentDirectory = elevationRequest.StartFolder; 37 | } 38 | catch (UnauthorizedAccessException ex) 39 | { 40 | throw new ApplicationException($"User \"{WindowsIdentity.GetCurrent().Name}\" can not access directory \"{elevationRequest.StartFolder}\""); 41 | } 42 | 43 | var process = Helpers.ProcessFactory.StartAttached(elevationRequest.FileName, elevationRequest.Arguments, elevationRequest.DisableInput); 44 | 45 | WaitHandle.WaitAny(new WaitHandle[] { process.GetProcessWaitHandle(), connection.DisconnectedWaitHandle }); 46 | if (process.HasExited) 47 | exitCode = process.ExitCode; 48 | 49 | await Task.Delay(1).ConfigureAwait(false); 50 | } 51 | catch (ApplicationException ex) 52 | { 53 | await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: {ex.Message}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false); 54 | exitCode = Constants.GSUDO_ERROR_EXITCODE; 55 | } 56 | catch (Exception ex) 57 | { 58 | await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: {ex.ToString()}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false); 59 | exitCode = Constants.GSUDO_ERROR_EXITCODE; 60 | } 61 | } 62 | else 63 | { 64 | var ex = new System.ComponentModel.Win32Exception(); 65 | await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: Attach Console Failed.\r\n{ex.Message}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false); 66 | Logger.Instance.Log("Attach Console Failed.", LogLevel.Error); 67 | exitCode = Constants.GSUDO_ERROR_EXITCODE; 68 | } 69 | 70 | if (connection.IsAlive) 71 | { 72 | await connection.ControlStream.WriteAsync($"{Constants.TOKEN_EXITCODE}{exitCode}{Constants.TOKEN_EXITCODE}").ConfigureAwait(false); 73 | } 74 | 75 | await connection.FlushAndCloseAll().ConfigureAwait(false); 76 | } 77 | finally 78 | { 79 | Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false); 80 | Native.ConsoleApi.FreeConsole(); 81 | await connection.FlushAndCloseAll().ConfigureAwait(false); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/gsudo/ProcessHosts/IProcessHost.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Rpc; 2 | using System.Threading.Tasks; 3 | 4 | namespace gsudo.ProcessHosts 5 | { 6 | /// 7 | /// To bridge both elevated and non-elevated worlds, 8 | /// the "Host" is the elevated part and communicates to the non-elevated "Renderer". 9 | /// E.g.: For piped or VT mode, the Host starts an (elevated process, 10 | /// captures its output and sends it (via the connection) to the Renderer, 11 | /// using a custom data and control communication protocol. 12 | /// Also manages Ctrl-C forwarding from the connection to the process. 13 | /// 14 | interface IProcessHost 15 | { 16 | Task Start(Connection connection, ElevationRequest elevationRequest); 17 | 18 | bool SupportsSimultaneousElevations { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gsudo/ProcessHosts/NewWindowProcessHost.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using gsudo.Rpc; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | 7 | namespace gsudo.ProcessHosts 8 | { 9 | /// 10 | /// Hosts a process, in a new window. 11 | /// So no I/O streaming is needed, other than process exit code. 12 | /// 13 | class NewWindowProcessHost : IProcessHost 14 | { 15 | private Process process; 16 | 17 | public bool SupportsSimultaneousElevations { get; } = true; 18 | 19 | public async Task Start(Connection connection, ElevationRequest request) 20 | { 21 | try 22 | { 23 | int exitCode = 0; 24 | process = ProcessFactory.StartDetached(request.FileName, request.Arguments, request.StartFolder, false); 25 | 26 | // Windows allows us to SetFocus on the new window if only if 27 | // this gsudo (service) has the focus. So we request the gsudo client to set focus as well. 28 | await connection.ControlStream 29 | .WriteAsync($"{Constants.TOKEN_FOCUS}{process.MainWindowHandle}{Constants.TOKEN_FOCUS}") 30 | .ConfigureAwait(false); 31 | 32 | if (request.Wait) 33 | { 34 | process.WaitForExit(); 35 | exitCode = process.ExitCode; 36 | } 37 | 38 | await connection.ControlStream 39 | .WriteAsync($"{Constants.TOKEN_EXITCODE}{exitCode}{Constants.TOKEN_EXITCODE}") 40 | .ConfigureAwait(false); 41 | 42 | await connection.FlushAndCloseAll().ConfigureAwait(false); 43 | return; 44 | } 45 | catch (Exception ex) 46 | { 47 | Logger.Instance.Log(ex.ToString(), LogLevel.Error); 48 | if (connection.IsAlive) 49 | await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: {ex.ToString()}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false); 50 | 51 | await connection.FlushAndCloseAll().ConfigureAwait(false); 52 | return; 53 | } 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/gsudo/ProcessHosts/TokenSwitchHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using gsudo.Helpers; 4 | using gsudo.Rpc; 5 | using gsudo.Tokens; 6 | 7 | namespace gsudo.ProcessHosts 8 | { 9 | /// 10 | /// Replaces the token on a process started in suspended mode. 11 | /// Based on https://stackoverflow.com/questions/5141997/is-there-a-way-to-set-a-token-for-another-process 12 | /// 13 | class TokenSwitchHost : IProcessHost 14 | { 15 | public bool SupportsSimultaneousElevations { get; } = true; 16 | 17 | public async Task Start(Connection connection, ElevationRequest elevationRequest) 18 | { 19 | try 20 | { 21 | TokenSwitcher.ReplaceProcessToken(elevationRequest); 22 | 23 | await connection.ControlStream.WriteAsync(Constants.TOKEN_SUCCESS).ConfigureAwait(false); 24 | } 25 | catch (Exception ex) 26 | { 27 | Logger.Instance.Log($"{ex.ToString()}", LogLevel.Debug); 28 | await connection.ControlStream 29 | .WriteAsync( 30 | $"{Constants.TOKEN_ERROR}Server Error: Setting token failed.\r\n{ex.Message}\r\n{Constants.TOKEN_ERROR}") 31 | .ConfigureAwait(false); 32 | } 33 | finally 34 | { 35 | Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false); 36 | await connection.FlushAndCloseAll().ConfigureAwait(false); 37 | } 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/gsudo/ProcessRenderers/AttachedConsoleRenderer.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using gsudo.Rpc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace gsudo.ProcessRenderers 11 | { 12 | /// 13 | /// Renderer empty shell, hosts a remote process until it finishes. 14 | /// All rendering is done by the remote process, because its attached to our console. 15 | /// 16 | // This mode is not enabled unless you use --attached. 17 | class AttachedConsoleRenderer : IProcessRenderer 18 | { 19 | private readonly Connection _connection; 20 | private int? exitCode; 21 | 22 | public AttachedConsoleRenderer(Connection connection) 23 | { 24 | _connection = connection; 25 | } 26 | 27 | public Task Start() 28 | { 29 | Console.CancelKeyPress += HandleConsoleCancelKeyPress; 30 | 31 | var t1 = new StreamReader(_connection.ControlStream).ConsumeOutput(HandleControlStream); 32 | _connection.DisconnectedWaitHandle.WaitOne(); 33 | 34 | Console.CancelKeyPress -= HandleConsoleCancelKeyPress; 35 | 36 | return Task.FromResult(exitCode ?? 0); 37 | } 38 | 39 | private static void HandleConsoleCancelKeyPress(object sender, ConsoleCancelEventArgs e) 40 | { 41 | e.Cancel = true; 42 | } 43 | 44 | enum Mode { Normal, Focus, Error, ExitCode }; 45 | Mode CurrentMode = Mode.Normal; 46 | 47 | static readonly string[] TOKENS = new string[] { "\0", Constants.TOKEN_ERROR, Constants.TOKEN_EXITCODE, Constants.TOKEN_FOCUS}; 48 | private async Task HandleControlStream(string s) 49 | { 50 | Action Toggle = (m) => CurrentMode = CurrentMode == Mode.Normal ? m : Mode.Normal; 51 | 52 | var tokens = new Stack(StringTokenizer.Split(s, TOKENS).Reverse()); 53 | 54 | while (tokens.Count > 0) 55 | { 56 | var token = tokens.Pop(); 57 | 58 | if (token == "\0") continue; // session keep alive 59 | 60 | if (token == Constants.TOKEN_FOCUS) 61 | { 62 | Toggle(Mode.Focus); 63 | continue; 64 | } 65 | if (token == Constants.TOKEN_EXITCODE) 66 | { 67 | Toggle(Mode.ExitCode); 68 | continue; 69 | } 70 | if (token == Constants.TOKEN_ERROR) 71 | { 72 | //fix intercalation of messages; 73 | await Console.Error.FlushAsync().ConfigureAwait(false); 74 | await Console.Out.FlushAsync().ConfigureAwait(false); 75 | 76 | Toggle(Mode.Error); 77 | Console.ResetColor(); 78 | continue; 79 | } 80 | 81 | if (CurrentMode == Mode.Focus) 82 | { 83 | var hwnd = (IntPtr)int.Parse(token, CultureInfo.InvariantCulture); 84 | Logger.Instance.Log($"SetForegroundWindow({hwnd}) returned {Native.WindowApi.SetForegroundWindow(hwnd)}", LogLevel.Debug); 85 | continue; 86 | } 87 | if (CurrentMode == Mode.Error) 88 | { 89 | lock (this) 90 | { 91 | Console.ForegroundColor = ConsoleColor.Red; 92 | Console.Write(token); 93 | Console.ResetColor(); 94 | } 95 | continue; 96 | } 97 | if (CurrentMode == Mode.ExitCode) 98 | { 99 | exitCode = int.Parse(token, CultureInfo.InvariantCulture); 100 | continue; 101 | } 102 | 103 | //lock(this) 104 | { 105 | Console.Write(token); 106 | } 107 | } 108 | } 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/gsudo/ProcessRenderers/IProcessRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace gsudo.ProcessRenderers 4 | { 5 | /// 6 | /// To bridge both elevated and non-elevated worlds, 7 | /// the "Host" is the elevated part and communicates to the non-elevated "Renderer". 8 | /// E.g.: For piped or VT mode, a renderer receives I/O from a remote process 9 | /// and shows it in the current console, using custom data/control protocol. 10 | /// Also manages Ctrl-C capturing and forwarding to the connection. 11 | /// 12 | /// This code runs in the non-elevated gsudo instance. 13 | interface IProcessRenderer 14 | { 15 | Task Start(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/gsudo/Program.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Commands; 2 | using gsudo.Helpers; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace gsudo 7 | { 8 | class Program 9 | { 10 | async static Task Main() 11 | { 12 | SymbolicLinkSupport.EnableAssemblyLoadFix(); 13 | 14 | return await Start().ConfigureAwait(false); 15 | } 16 | 17 | private static async Task Start() 18 | { 19 | ICommand cmd = null; 20 | 21 | var commandLine = ArgumentsHelper.GetRealCommandLine(); 22 | var args = ArgumentsHelper.SplitArgs(commandLine); 23 | 24 | try 25 | { 26 | try 27 | { 28 | cmd = new CommandLineParser(args).Parse(); 29 | } 30 | finally 31 | { 32 | Logger.Instance.Log($"Command Line: {commandLine}", LogLevel.Debug); 33 | } 34 | 35 | if (cmd != null) 36 | { 37 | try 38 | { 39 | return await cmd.Execute().ConfigureAwait(false); 40 | } 41 | finally 42 | { 43 | (cmd as IDisposable)?.Dispose(); 44 | } 45 | } 46 | 47 | return 0; 48 | } 49 | catch (ApplicationException ex) 50 | { 51 | Logger.Instance.Log(ex.Message, LogLevel.Error); // one liner errors. 52 | return Constants.GSUDO_ERROR_EXITCODE; 53 | } 54 | catch (Exception ex) 55 | { 56 | Logger.Instance.Log(ex.ToString(), LogLevel.Error); // verbose errors. 57 | return Constants.GSUDO_ERROR_EXITCODE; 58 | } 59 | finally 60 | { 61 | if (InputArguments.KillCache) 62 | { 63 | await new KillCacheCommand(verbose: false).Execute().ConfigureAwait(false); 64 | } 65 | 66 | try 67 | { 68 | // cleanup console before returning. 69 | Console.CursorVisible = true; 70 | Console.ResetColor(); 71 | await Task.Delay(1).ConfigureAwait(false); // force reset color on WSL. 72 | 73 | if (InputArguments.Debug && !Console.IsInputRedirected) 74 | { 75 | if (cmd.GetType() == typeof(ServiceCommand)) 76 | { 77 | Console.WriteLine("Service shutdown. This window will close in 10 seconds"); 78 | System.Threading.Thread.Sleep(10000); 79 | } 80 | } 81 | } 82 | catch 83 | { 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/gsudo/Properties/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("gsudo.Tests")] -------------------------------------------------------------------------------- /src/gsudo/PseudoConsole/PseudoConsole.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using static gsudo.Native.PseudoConsoleApi; 4 | 5 | namespace gsudo.PseudoConsole 6 | { 7 | /// 8 | /// Utility functions around the new Pseudo Console APIs 9 | /// 10 | internal sealed class PseudoConsole : IDisposable 11 | { 12 | public static readonly IntPtr PseudoConsoleThreadAttribute = (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE; 13 | 14 | public IntPtr Handle { get; } 15 | 16 | private PseudoConsole(IntPtr handle) 17 | { 18 | this.Handle = handle; 19 | } 20 | 21 | public bool SetCursorPosition(int X, int Y) 22 | { 23 | return Native.ConsoleApi.SetConsoleCursorPosition(Handle, new COORD { X = (short)X, Y = (short)Y }); 24 | } 25 | 26 | internal static PseudoConsole Create(SafeFileHandle inputReadSide, SafeFileHandle outputWriteSide, int width, int height) 27 | { 28 | bool InheritCursor = true; 29 | var createResult = CreatePseudoConsole( 30 | new COORD { X = (short)width, Y = (short)height }, 31 | inputReadSide, outputWriteSide, 32 | InheritCursor ? (uint)1 : 0, out IntPtr hPC); 33 | 34 | if(createResult != 0) 35 | { 36 | throw new System.ComponentModel.Win32Exception(); 37 | } 38 | return new PseudoConsole(hPC); 39 | } 40 | 41 | public bool Resize(int X, int Y) 42 | { 43 | return Native.PseudoConsoleApi.ResizePseudoConsole(Handle, new COORD() { X = (short)X, Y = (short)Y }) == 0; 44 | } 45 | 46 | public void Dispose() 47 | { 48 | _ = ClosePseudoConsole(Handle); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/gsudo/PseudoConsole/PseudoConsolePipe.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using static gsudo.Native.ProcessApi; 4 | 5 | namespace gsudo.PseudoConsole 6 | { 7 | /// 8 | /// A pipe used to talk to the pseudoconsole, as described in: 9 | /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session 10 | /// 11 | /// 12 | /// We'll have two instances of this class, one for input and one for output. 13 | /// 14 | internal sealed class PseudoConsolePipe : IDisposable 15 | { 16 | public readonly SafeFileHandle ReadSide; 17 | public readonly SafeFileHandle WriteSide; 18 | 19 | public PseudoConsolePipe() 20 | { 21 | if (!CreatePipe(out ReadSide, out WriteSide, IntPtr.Zero, 0)) 22 | { 23 | throw new InvalidOperationException("failed to create pipe"); 24 | } 25 | } 26 | 27 | #region IDisposable 28 | 29 | void Dispose(bool disposing) 30 | { 31 | if (disposing) 32 | { 33 | ReadSide?.Dispose(); 34 | WriteSide?.Dispose(); 35 | } 36 | } 37 | 38 | public void Dispose() 39 | { 40 | Dispose(true); 41 | GC.SuppressFinalize(this); 42 | } 43 | 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/gsudo/PseudoConsole/PseudoConsoleProcess.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using static gsudo.Native.ProcessApi; 6 | 7 | namespace gsudo.PseudoConsole 8 | { 9 | /// 10 | /// Represents an instance of a process. 11 | /// 12 | internal sealed class PseudoConsoleProcess : IDisposable 13 | { 14 | public PseudoConsoleProcess(STARTUPINFOEX startupInfo, PROCESS_INFORMATION processInfo) 15 | { 16 | StartupInfo = startupInfo; 17 | ProcessInfo = processInfo; 18 | } 19 | 20 | public STARTUPINFOEX StartupInfo { get; } 21 | public PROCESS_INFORMATION ProcessInfo { get; } 22 | 23 | public int? GetExitCode() 24 | { 25 | const int STILL_ACTIVE = 0x00000103; 26 | if (GetExitCodeProcess(new Microsoft.Win32.SafeHandles.SafeProcessHandle(this.ProcessInfo.hProcess, ownsHandle: false), out int exitCode) && exitCode != STILL_ACTIVE) 27 | { 28 | return exitCode; 29 | } 30 | return null; 31 | } 32 | 33 | #region IDisposable Support 34 | 35 | private bool disposedValue = false; // To detect redundant calls 36 | 37 | void Dispose(bool disposing) 38 | { 39 | if (!disposedValue) 40 | { 41 | if (disposing) 42 | { 43 | // dispose managed state (managed objects). 44 | } 45 | 46 | // dispose unmanaged state 47 | 48 | // Free the attribute list 49 | if (StartupInfo.lpAttributeList != IntPtr.Zero) 50 | { 51 | DeleteProcThreadAttributeList(StartupInfo.lpAttributeList); 52 | Marshal.FreeHGlobal(StartupInfo.lpAttributeList); 53 | } 54 | 55 | // Close process and thread handles 56 | if (ProcessInfo.hProcess != IntPtr.Zero) 57 | { 58 | CloseHandle(ProcessInfo.hProcess); 59 | } 60 | if (ProcessInfo.hThread != IntPtr.Zero) 61 | { 62 | CloseHandle(ProcessInfo.hThread); 63 | } 64 | 65 | disposedValue = true; 66 | } 67 | } 68 | 69 | ~PseudoConsoleProcess() 70 | { 71 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 72 | Dispose(false); 73 | } 74 | 75 | // This code added to correctly implement the disposable pattern. 76 | public void Dispose() 77 | { 78 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 79 | Dispose(true); 80 | // use the following line if the finalizer is overridden above. 81 | GC.SuppressFinalize(this); 82 | } 83 | #endregion 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/gsudo/Rpc/Connection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Runtime.Serialization.Formatters.Binary; 5 | #if NETCOREAPP 6 | using System.Text.Json; 7 | #endif 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace gsudo.Rpc 12 | { 13 | class Connection : IDisposable 14 | { 15 | private PipeStream _dataStream; 16 | private PipeStream _controlStream; 17 | public Connection(PipeStream ControlStream, PipeStream DataStream) 18 | { 19 | _dataStream = DataStream; 20 | _controlStream = ControlStream; 21 | } 22 | 23 | public Stream DataStream => _dataStream; 24 | public Stream ControlStream => _controlStream; 25 | 26 | private ManualResetEvent DisconnectedResetEvent { get; } = new ManualResetEvent(false); 27 | public WaitHandle DisconnectedWaitHandle => DisconnectedResetEvent; 28 | 29 | public bool IsAlive { get; private set; } = true; 30 | public void SignalDisconnected() 31 | { 32 | IsAlive = false; 33 | DisconnectedResetEvent.Set(); 34 | } 35 | 36 | public async Task FlushAndCloseAll() 37 | { 38 | IsAlive = false; 39 | await FlushDataStream().ConfigureAwait(false); 40 | await FlushControlStream().ConfigureAwait(false); 41 | DataStream.Close(); 42 | ControlStream.Close(); 43 | } 44 | 45 | public Task FlushDataStream() => Flush(_dataStream); 46 | public Task FlushControlStream() => Flush(_controlStream); 47 | 48 | private async Task Flush(PipeStream npStream) 49 | { 50 | try 51 | { 52 | await Task.Delay(1).ConfigureAwait(false); 53 | await npStream.FlushAsync().ConfigureAwait(false); 54 | npStream.WaitForPipeDrain(); 55 | await Task.Delay(1).ConfigureAwait(false); 56 | } 57 | catch (ObjectDisposedException) { } 58 | catch (Exception) { } 59 | } 60 | 61 | public async Task WriteElevationRequest(ElevationRequest elevationRequest) 62 | { 63 | #if NETFRAMEWORK 64 | // Using Binary instead of Newtonsoft.JSON to reduce load times. 65 | var ms = new System.IO.MemoryStream(); 66 | new BinaryFormatter() 67 | { TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesAlways, Binder = new MySerializationBinder() } 68 | .Serialize(ms, elevationRequest); 69 | ms.Seek(0, System.IO.SeekOrigin.Begin); 70 | 71 | byte[] lengthArray = BitConverter.GetBytes(ms.Length); 72 | Logger.Instance.Log($"ElevationRequest length {ms.Length}", LogLevel.Debug); 73 | 74 | await ControlStream.WriteAsync(lengthArray, 0, sizeof(int)).ConfigureAwait(false); 75 | await ControlStream.WriteAsync(ms.ToArray(), 0, (int)ms.Length).ConfigureAwait(false); 76 | await ControlStream.FlushAsync().ConfigureAwait(false); 77 | #else 78 | byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(elevationRequest, ElevationRequestJsonContext.Default.ElevationRequest); 79 | 80 | await ControlStream.WriteAsync(BitConverter.GetBytes(utf8Json.Length), 0, sizeof(int)).ConfigureAwait(false); 81 | await ControlStream.WriteAsync(utf8Json, 0, utf8Json.Length).ConfigureAwait(false); 82 | await ControlStream.FlushAsync().ConfigureAwait(false); 83 | #endif 84 | } 85 | 86 | public void Dispose() 87 | { 88 | DataStream?.Close(); 89 | DataStream?.Dispose(); 90 | ControlStream?.Close(); 91 | ControlStream?.Dispose(); 92 | IsAlive = false; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/gsudo/Rpc/ConnectionKeepAliveThread.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace gsudo.Rpc 4 | { 5 | // A thread that detect when the NamedPipeConnection is disconnected. 6 | // Couldn't find a better way to do this than periodically writing to the pipe. 7 | // You can read indifinetely on a pipe that was closed by the other end. 8 | // 9 | // Separated in a different thread because named pipes sometimes hangs up 10 | // when using WriteAsync on the main loop. 11 | class ConnectionKeepAliveThread 12 | { 13 | private readonly Connection _connection; 14 | 15 | public static void Start(Connection connection) 16 | { 17 | var obj = new ConnectionKeepAliveThread(connection); 18 | var thread = new Thread(new ThreadStart(obj.DoWork)); 19 | thread.Start(); 20 | } 21 | 22 | private ConnectionKeepAliveThread(Connection connection) 23 | { 24 | _connection = connection; 25 | } 26 | 27 | private void DoWork() 28 | { 29 | byte[] data = new byte[] { 0 }; 30 | try 31 | { 32 | 33 | while (_connection.IsAlive) 34 | { 35 | Thread.Sleep(10); 36 | _connection.ControlStream.Write(data, 0, 1); 37 | } 38 | } 39 | catch 40 | { 41 | _connection.SignalDisconnected(); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/gsudo/Rpc/IRpcClient.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using Microsoft.Win32.SafeHandles; 3 | using System.Threading.Tasks; 4 | 5 | namespace gsudo.Rpc 6 | { 7 | internal interface IRpcClient 8 | { 9 | Task Connect(ServiceLocation service); 10 | } 11 | } -------------------------------------------------------------------------------- /src/gsudo/Rpc/IRpcServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace gsudo.Rpc 8 | { 9 | interface IRpcServer : IDisposable 10 | { 11 | Task Listen(); 12 | void Close(); 13 | 14 | event EventHandler ConnectionAccepted; 15 | event EventHandler ConnectionClosed; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/gsudo/Rpc/NamedPipeNameFactory.cs: -------------------------------------------------------------------------------- 1 | using gsudo.Helpers; 2 | using System; 3 | using System.Globalization; 4 | using System.Security.Principal; 5 | using System.Text; 6 | 7 | namespace gsudo.Rpc 8 | { 9 | static class NamedPipeNameFactory 10 | { 11 | public static string GetPipeName(string allowedSid, int allowedPid, string targetSid, bool isAdmin) 12 | { 13 | if (allowedPid < 0) allowedPid = 0; 14 | 15 | var ti = InputArguments.TrustedInstaller ? "_TI" : string.Empty; 16 | var s = InputArguments.RunAsSystem ? "_S" : string.Empty; 17 | var admin = !isAdmin ? "_NonAdmin" : string.Empty; 18 | 19 | var ownExe = GetHash(ProcessHelper.GetOwnExeName()); 20 | var data = $"allowedSid-{allowedSid}_targetSid-{targetSid}{allowedPid}{s}{ti}{admin}_{ownExe}"; 21 | #if !DEBUG 22 | data = GetHash(data); 23 | #endif 24 | return $"{GetPipePrefix(isAdmin)}_{data}"; 25 | } 26 | 27 | private static string GetHash(string data) 28 | { 29 | using (var hashingAlg = System.Security.Cryptography.SHA256.Create()) 30 | { 31 | var hash = hashingAlg.ComputeHash(UTF8Encoding.UTF8.GetBytes(data)); 32 | StringBuilder sb = new StringBuilder(); 33 | for (int i = 0; i < hash.Length; i++) 34 | { 35 | sb.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture)); 36 | } 37 | return sb.ToString(); 38 | } 39 | } 40 | 41 | private static string GetPipePrefix(bool isAdmin) 42 | { 43 | const string PROTECTED = "ProtectedPrefix\\Administrators\\gsudo"; 44 | const string REGULAR = "gsudo"; 45 | if (isAdmin) 46 | return PROTECTED; 47 | else 48 | return REGULAR; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/gsudo/Rpc/NamedPipeUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace gsudo.Rpc 7 | { 8 | static class NamedPipeUtils 9 | { 10 | public static List ListNamedPipes() 11 | { 12 | var namedPipes = new List(); 13 | Native.FileApi.WIN32_FIND_DATA lpFindFileData; 14 | //var name = "ProtectedPrefix\\Administrators\\gsudo*"; 15 | var name = "*"; 16 | var ptr = Native.FileApi.FindFirstFile($@"\\.\pipe\{name}", out lpFindFileData); 17 | do 18 | { 19 | if (lpFindFileData.cFileName.ToUpperInvariant().Contains("GSUDO") && 20 | !lpFindFileData.cFileName.ToUpperInvariant().Contains("_CONTROL")) 21 | { 22 | namedPipes.Add(lpFindFileData.cFileName); 23 | } 24 | } 25 | while (Native.FileApi.FindNextFile(ptr, out lpFindFileData)); 26 | 27 | Native.FileApi.FindClose(ptr); 28 | namedPipes.Sort(); 29 | 30 | return namedPipes; 31 | } 32 | 33 | public static bool ExistsNamedPipe(string name) 34 | { 35 | //Logger.Instance.Log($"Searching for {name}", LogLevel.Debug); 36 | try 37 | { 38 | return System.IO.Directory.GetFiles("\\\\.\\\\pipe\\", name).Any(); 39 | } 40 | catch 41 | { 42 | // Windows 7 workaround 43 | foreach (var pipe in System.IO.Directory.GetFiles("\\\\.\\\\pipe\\")) 44 | { 45 | if (pipe.EndsWith(name, StringComparison.Ordinal)) 46 | { 47 | //Logger.Instance.Log($"Found Named Pipe {name}", LogLevel.Debug); 48 | return true; 49 | } 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | static string GetRootFolder(string path) 57 | { 58 | while (true) 59 | { 60 | string temp = Path.GetDirectoryName(path); 61 | if (String.IsNullOrEmpty(temp)) 62 | break; 63 | path = temp; 64 | } 65 | return path; 66 | } 67 | 68 | public static int GetClientProcessId(this System.IO.Pipes.NamedPipeServerStream pipeServer) 69 | { 70 | UInt32 nProcID; 71 | IntPtr hPipe = pipeServer.SafePipeHandle.DangerousGetHandle(); 72 | if (Native.ProcessApi.GetNamedPipeClientProcessId(hPipe, out nProcID)) 73 | return (int)nProcID; 74 | return 0; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/gsudo/Tokens/IntegrityLevel.cs: -------------------------------------------------------------------------------- 1 | using static gsudo.Native.TokensApi; 2 | 3 | namespace gsudo 4 | { 5 | public enum IntegrityLevel 6 | { 7 | Untrusted = 0, 8 | Low = 4096, 9 | Medium = 8192, 10 | MediumRestricted = 8193, // Experimental, like medium but without the split token. It can call RunAs but won't elevate. 11 | MediumPlus = 8448, 12 | High = 12288, 13 | System = 16384, 14 | Protected = 20480, 15 | Secure = 28672 16 | } 17 | 18 | static class IntegrityLevelExtensions 19 | { 20 | public static SaferLevels ToSaferLevel(this IntegrityLevel integrityLevel) 21 | { 22 | if (integrityLevel >= IntegrityLevel.High) 23 | return SaferLevels.FullyTrusted; 24 | if (integrityLevel >= IntegrityLevel.Medium) 25 | return SaferLevels.NormalUser; 26 | if (integrityLevel >= IntegrityLevel.Low) 27 | return SaferLevels.Constrained; 28 | return SaferLevels.Untrusted; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/gsudo/Tokens/Privilege.cs: -------------------------------------------------------------------------------- 1 | namespace gsudo.Tokens 2 | { 3 | public enum Privilege 4 | { 5 | SeAssignPrimaryTokenPrivilege, 6 | SeAuditPrivilege, 7 | SeBackupPrivilege, 8 | SeChangeNotifyPrivilege, 9 | SeCreateGlobalPrivilege, 10 | SeCreatePagefilePrivilege, 11 | SeCreatePermanentPrivilege, 12 | SeCreateSymbolicLinkPrivilege, 13 | SeCreateTokenPrivilege, 14 | SeDebugPrivilege, 15 | SeEnableDelegationPrivilege, 16 | SeImpersonatePrivilege, 17 | SeIncreaseBasePriorityPrivilege, 18 | SeIncreaseQuotaPrivilege, 19 | SeIncreaseWorkingSetPrivilege, 20 | SeLoadDriverPrivilege, 21 | SeLockMemoryPrivilege, 22 | SeMachineAccountPrivilege, 23 | SeManageVolumePrivilege, 24 | SeProfileSingleProcessPrivilege, 25 | SeRelabelPrivilege, 26 | SeRemoteShutdownPrivilege, 27 | SeRestorePrivilege, 28 | SeSecurityPrivilege, 29 | SeShutdownPrivilege, 30 | SeSyncAgentPrivilege, 31 | SeSystemEnvironmentPrivilege, 32 | SeSystemProfilePrivilege, 33 | SeSystemtimePrivilege, 34 | SeTakeOwnershipPrivilege, 35 | SeTcbPrivilege, 36 | SeTimeZonePrivilege, 37 | SeTrustedCredManAccessPrivilege, 38 | SeUndockPrivilege, 39 | SeDelegateSessionUserImpersonatePrivilege, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/gsudo/Tokens/PrivilegeManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | using System.Security.Principal; 5 | using static gsudo.Native.TokensApi; 6 | using static gsudo.Native.NativeMethods; 7 | using gsudo.Native; 8 | 9 | namespace gsudo.Tokens 10 | { 11 | //Enable a privilege in the current thread by implementing the following line in your code: 12 | //PrivilegeManager.EnablePrivilege(SecurityEntity.SE_SHUTDOWN_NAME); 13 | 14 | public static class PrivilegeManager 15 | { 16 | public static void DisableAllPrivileges(IntPtr tokenHandle) 17 | { 18 | var TOKEN_PRIVILEGES = new NativeMethods.TOKEN_PRIVILEGES(); 19 | 20 | if (!NativeMethods.AdjustTokenPrivileges(tokenHandle, true, ref TOKEN_PRIVILEGES, 0, IntPtr.Zero, IntPtr.Zero)) 21 | throw new Win32Exception(); 22 | } 23 | 24 | public static void SetPrivilegeState(Privilege securityEntity, bool enabled) 25 | { 26 | const int ERROR_NO_TOKEN = 0x3f0; 27 | 28 | var locallyUniqueIdentifier = new LUID(); 29 | 30 | if (!LookupPrivilegeValue(null, securityEntity.ToString(), ref locallyUniqueIdentifier)) 31 | throw new Win32Exception(); 32 | 33 | if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token)) 34 | { 35 | var error = Marshal.GetLastWin32Error(); 36 | if (error != ERROR_NO_TOKEN) 37 | { 38 | throw new Win32Exception(error); 39 | } 40 | 41 | // No token is on the thread, copy from process 42 | if (!OpenProcessToken(GetCurrentProcess(), (uint)TokenAccessLevels.Duplicate, out var processToken)) 43 | { 44 | throw new Win32Exception(); 45 | } 46 | 47 | if (!DuplicateTokenEx(processToken, (uint)(TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges), 48 | IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out token)) 49 | { 50 | throw new Win32Exception(); 51 | } 52 | 53 | if (!SetThreadToken(IntPtr.Zero, token)) 54 | { 55 | throw new Win32Exception(); 56 | } 57 | } 58 | 59 | var tp = new NativeMethods.TOKEN_PRIVILEGES(); 60 | tp.PrivilegeCount = 1; 61 | 62 | tp.Privileges = new LUID_AND_ATTRIBUTES[1]; 63 | tp.Privileges[0].Attributes = (uint) (enabled ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED); 64 | tp.Privileges[0].Luid = locallyUniqueIdentifier; 65 | 66 | if (!NativeMethods.AdjustTokenPrivileges(token.DangerousGetHandle(), false, ref tp, (uint)Marshal.SizeOf(tp), 67 | IntPtr.Zero, IntPtr.Zero)) 68 | throw new Win32Exception(); 69 | 70 | Logger.Instance.Log($"Privilege {securityEntity} was {(enabled ? "ENABLED" : "DISABLED")}", LogLevel.Warning); 71 | token.Close(); 72 | // todo: proper close. 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/gsudo/gsudo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net9.0;net46 5 | false 6 | StrongName.snk 7 | Gerardo Grignoli 8 | gsudo is a sudo for Windows, allows to run commands with elevated permissions in the current console. 9 | false 10 | bin\ 11 | true 12 | false 13 | OnBuildSuccess 14 | 1.0.0-prerelease 15 | 2021 Gerardo Grignoli 16 | MIT 17 | https://github.com/gerardog/gsudo 18 | https://github.com/gerardog/gsudo 19 | git 20 | sudo for windows 21 | gsudo 22 | true 23 | $(MSBuildProjectDirectory)=C:\ 24 | 1.0.0.0 25 | 1.0.0.0 26 | 1701;1702;CA1303;CA1707;CA1028;CA1001;CA1031;CA1416 27 | icon\gsudo.ico 28 | 29 | 30 | true 31 | False 32 | true 33 | true 34 | Size 35 | 36 | 37 | DEBUG 38 | pdbonly 39 | true 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | all 58 | runtime; build; native; contentfiles; analyzers; buildtransitive 59 | 60 | 61 | all 62 | runtime; build; native; contentfiles; analyzers; buildtransitive 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/gsudo/gsudo.nuspec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/src/gsudo/gsudo.nuspec -------------------------------------------------------------------------------- /src/gsudo/icon/gsudo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gerardog/gsudo/10725f2db4eb6cd3f24e1662a8ede3195722a48a/src/gsudo/icon/gsudo.ico -------------------------------------------------------------------------------- /src/gsudo/icon/gsudo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------