├── .gitignore ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── isadmin.ps1 ├── sudo.csproj ├── sudo.sln └── testscript.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /obj/* 3 | *.user 4 | *.suo 5 | -------------------------------------------------------------------------------- /App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace sudo { 9 | 10 | class Program { 11 | // elevating with cmd.exe instead of calling sudo.exe directly 12 | // gives a blue UAC instead of the yellow "unknown publisher" one. 13 | const bool ElevateWithCmd = false; 14 | 15 | [DllImport("kernel32.dll", SetLastError = true)] 16 | static extern bool AttachConsole(uint dwProcessId); 17 | 18 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 19 | static extern bool FreeConsole(); 20 | 21 | static void Main(string[] args) { 22 | if(args.Length == 0) { 23 | Console.Error.WriteLine("usage: sudo "); 24 | Environment.Exit(1); 25 | } 26 | 27 | if(args[0] == "-do") { 28 | if(args.Length < 4) { Console.Error.Write("not enough arguments"); Environment.Exit(1); } 29 | 30 | var exit_code = Do(args[1], args[2], string.Join(" ", args.Skip(3))); 31 | Environment.Exit(exit_code); 32 | return; 33 | } 34 | 35 | var pid = Process.GetCurrentProcess().Id; 36 | var sudo_exe = Assembly.GetExecutingAssembly().Location; 37 | var pwd = Environment.CurrentDirectory; 38 | 39 | var elev_args = "-do \"" + pwd + "\" " + pid + " " + Serialize(args); 40 | 41 | var p = new Process(); 42 | if(ElevateWithCmd) { 43 | p.StartInfo.FileName = "cmd"; 44 | p.StartInfo.Arguments = "/s /c \"\"" + sudo_exe + "\" " + elev_args + "\""; 45 | } else { 46 | p.StartInfo.FileName = sudo_exe; 47 | p.StartInfo.Arguments = elev_args; 48 | } 49 | p.StartInfo.Verb = "runas"; 50 | p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 51 | 52 | try { 53 | p.Start(); 54 | } catch(Win32Exception) { Environment.Exit(1); } // user didn't provide consent 55 | 56 | p.WaitForExit(); 57 | Environment.Exit(p.ExitCode); 58 | } 59 | 60 | static int Do(string dir, string parent_pid, string cmd) { 61 | uint pid; 62 | if(!uint.TryParse(parent_pid, out pid)) { 63 | Console.WriteLine("Couldn't get pid"); return 1; 64 | } 65 | 66 | FreeConsole(); 67 | AttachConsole(pid); 68 | 69 | var p = new Process(); 70 | var start = p.StartInfo; 71 | start.FileName = "powershell.exe"; 72 | start.Arguments = "-noprofile " + cmd + "\nexit $lastexitcode"; 73 | start.UseShellExecute = false; 74 | start.WorkingDirectory = dir; 75 | 76 | p.Start(); 77 | p.WaitForExit(); 78 | return p.ExitCode; 79 | } 80 | 81 | static string Serialize(string[] args) { 82 | return string.Join(" ", args.Select(a => a.Contains(' ') ? "'" + a + "'" : a)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /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("sudo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("sudo")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 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("1b8dd3eb-bbd6-4ddd-b9a9-afd8d872ced1")] 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 | -------------------------------------------------------------------------------- /isadmin.ps1: -------------------------------------------------------------------------------- 1 | $id = [Security.Principal.WindowsIdentity]::GetCurrent() 2 | write-host "is admin? " -nonewline 3 | ([Security.Principal.WindowsPrincipal]($id)).isinrole("Administrators").tostring().tolower() -------------------------------------------------------------------------------- /sudo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {24F20E15-A9C9-4EC1-875A-80E5F19029AA} 8 | Exe 9 | Properties 10 | sudo 11 | sudo 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Always 51 | 52 | 53 | PreserveNewest 54 | 55 | 56 | 57 | 64 | -------------------------------------------------------------------------------- /sudo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2012 for Windows Desktop 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sudo", "sudo.csproj", "{24F20E15-A9C9-4EC1-875A-80E5F19029AA}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {24F20E15-A9C9-4EC1-875A-80E5F19029AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {24F20E15-A9C9-4EC1-875A-80E5F19029AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {24F20E15-A9C9-4EC1-875A-80E5F19029AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {24F20E15-A9C9-4EC1-875A-80E5F19029AA}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /testscript.ps1: -------------------------------------------------------------------------------- 1 | $name = read-host "enter your name" 2 | write-host "hi, $name" -f darkgreen 3 | for($i = 1; $i -lt 6; $i++) { 4 | write-host $i 5 | start-sleep -m 100 6 | } 7 | 8 | $id = [Security.Principal.WindowsIdentity]::GetCurrent() 9 | $admin = ([Security.Principal.WindowsPrincipal]($id)).isinrole("Administrators") 10 | 11 | "admin? $admin" 12 | 13 | write-error "error!" 14 | 15 | exit 123 --------------------------------------------------------------------------------