diff --git a/extras/UAC.exe b/extras/UAC.exe new file mode 100644 index 00000000..6ea61dfb Binary files /dev/null and b/extras/UAC.exe differ diff --git a/terminus-terminal/src/api.ts b/terminus-terminal/src/api.ts index 7d7c7876..f4d22391 100644 --- a/terminus-terminal/src/api.ts +++ b/terminus-terminal/src/api.ts @@ -21,6 +21,7 @@ export interface SessionOptions { width?: number height?: number pauseAfterExit?: boolean + runAsAdministrator?: boolean } export interface Profile { diff --git a/terminus-terminal/src/components/editProfileModal.component.pug b/terminus-terminal/src/components/editProfileModal.component.pug index d89100cf..fa1e86bf 100644 --- a/terminus-terminal/src/components/editProfileModal.component.pug +++ b/terminus-terminal/src/components/editProfileModal.component.pug @@ -32,6 +32,13 @@ i.fas.fa-plus.mr-2 | Add + .form-line(*ngIf='uac.isAvailable') + .header + .title Run as administrator + toggle( + [(ngModel)]='profile.sessionOptions.runAsAdministrator', + ) + .form-group label Working directory input.form-control( diff --git a/terminus-terminal/src/components/editProfileModal.component.ts b/terminus-terminal/src/components/editProfileModal.component.ts index 8d395c64..3cf43f3a 100644 --- a/terminus-terminal/src/components/editProfileModal.component.ts +++ b/terminus-terminal/src/components/editProfileModal.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { UACService } from '../services/uac.service' import { Profile } from '../api' @Component({ @@ -9,6 +10,7 @@ export class EditProfileModalComponent { profile: Profile constructor ( + public uac: UACService, private modalInstance: NgbActiveModal, ) { } diff --git a/terminus-terminal/src/components/shellSettingsTab.component.pug b/terminus-terminal/src/components/shellSettingsTab.component.pug index 17025e90..5a0141f2 100644 --- a/terminus-terminal/src/components/shellSettingsTab.component.pug +++ b/terminus-terminal/src/components/shellSettingsTab.component.pug @@ -15,7 +15,7 @@ h3.mb-3 Shell ) {{shell.name}} -.form-line(*ngIf='hostApp.platform === Platform.Windows') +.form-line(*ngIf='isConPTYAvailable') .header .title Use ConPTY .description Enables the experimental Windows ConPTY API diff --git a/terminus-terminal/src/components/shellSettingsTab.component.ts b/terminus-terminal/src/components/shellSettingsTab.component.ts index e887e53c..26ca6ee9 100644 --- a/terminus-terminal/src/components/shellSettingsTab.component.ts +++ b/terminus-terminal/src/components/shellSettingsTab.component.ts @@ -5,6 +5,7 @@ import { ConfigService, ElectronService, HostAppService, Platform } from 'termin import { EditProfileModalComponent } from './editProfileModal.component' import { IShell, Profile } from '../api' import { TerminalService } from '../services/terminal.service' +import { UACService } from '../services/uac.service' @Component({ template: require('./shellSettingsTab.component.pug'), @@ -13,9 +14,11 @@ export class ShellSettingsTabComponent { shells: IShell[] = [] profiles: Profile[] = [] Platform = Platform + isConPTYAvailable: boolean private configSubscription: Subscription constructor ( + uac: UACService, public config: ConfigService, public hostApp: HostAppService, private electron: ElectronService, @@ -27,6 +30,7 @@ export class ShellSettingsTabComponent { this.reload() }) this.reload() + this.isConPTYAvailable = uac.isAvailable } async ngOnInit () { diff --git a/terminus-terminal/src/components/terminalTab.component.ts b/terminus-terminal/src/components/terminalTab.component.ts index 641dea35..29ef02e6 100644 --- a/terminus-terminal/src/components/terminalTab.component.ts +++ b/terminus-terminal/src/components/terminalTab.component.ts @@ -7,6 +7,7 @@ import { AppService, ConfigService, BaseTabComponent, BaseTabProcess, ElectronSe import { Session, SessionsService } from '../services/sessions.service' import { TerminalService } from '../services/terminal.service' import { TerminalFrontendService } from '../services/terminalFrontend.service' +import { UACService } from '../services/uac.service' import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api' import { Frontend } from '../frontends/frontend' @@ -52,6 +53,7 @@ export class TerminalTabComponent extends BaseTabComponent { private terminalContainersService: TerminalFrontendService, public config: ConfigService, private toastr: ToastrService, + private uac: UACService, @Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[], ) { super() @@ -205,7 +207,7 @@ export class TerminalTabComponent extends BaseTabComponent { async buildContextMenu (): Promise { let shells = await this.terminalService.shells$.toPromise() - return [ + let items: Electron.MenuItemConstructorOptions[] = [ { label: 'New terminal', click: () => this.zone.run(() => { @@ -221,6 +223,23 @@ export class TerminalTabComponent extends BaseTabComponent { }), })), }, + ] + + if (this.uac.isAvailable) { + items.push({ + label: 'New as admin', + submenu: shells.map(shell => ({ + label: shell.name, + click: () => this.zone.run(async () => { + let options = this.terminalService.optionsFromShell(shell) + options.runAsAdministrator = true + this.terminalService.openTabWithOptions(options) + }), + })), + }) + } + + items = items.concat([ { label: 'New with profile', submenu: this.config.store.terminal.profiles.length ? this.config.store.terminal.profiles.map(profile => ({ @@ -255,7 +274,9 @@ export class TerminalTabComponent extends BaseTabComponent { }) } }, - ] + ]) + + return items } detachTermContainerHandlers () { diff --git a/terminus-terminal/src/services/terminal.service.ts b/terminus-terminal/src/services/terminal.service.ts index b776fb0d..71528ab2 100644 --- a/terminus-terminal/src/services/terminal.service.ts +++ b/terminus-terminal/src/services/terminal.service.ts @@ -3,6 +3,7 @@ import { Injectable, Inject } from '@angular/core' import { AppService, Logger, LogService, ConfigService } from 'terminus-core' import { IShell, ShellProvider, SessionOptions } from '../api' import { TerminalTabComponent } from '../components/terminalTab.component' +import { UACService } from './uac.service' @Injectable({ providedIn: 'root' }) export class TerminalService { @@ -14,6 +15,7 @@ export class TerminalService { constructor ( private app: AppService, private config: ConfigService, + private uac: UACService, @Inject(ShellProvider) private shellProviders: ShellProvider[], log: LogService, ) { @@ -61,7 +63,7 @@ export class TerminalService { return this.openTabWithOptions(sessionOptions) } - optionsFromShell (shell: IShell) { + optionsFromShell (shell: IShell): SessionOptions { return { command: shell.command, args: shell.args || [], @@ -70,6 +72,9 @@ export class TerminalService { } openTabWithOptions (sessionOptions: SessionOptions): TerminalTabComponent { + if (sessionOptions.runAsAdministrator && this.uac.isAvailable) { + sessionOptions = this.uac.patchSessionOptionsForUAC(sessionOptions) + } this.logger.log('Using session options:', sessionOptions) return this.app.openNewTab( diff --git a/terminus-terminal/src/services/uac.service.ts b/terminus-terminal/src/services/uac.service.ts new file mode 100644 index 00000000..8e27be23 --- /dev/null +++ b/terminus-terminal/src/services/uac.service.ts @@ -0,0 +1,43 @@ +import * as path from 'path' +import * as os from 'os' +import { Injectable } from '@angular/core' +import { ElectronService, HostAppService, Platform } from 'terminus-core' +import { SessionOptions } from '../api' + +@Injectable({ providedIn: 'root' }) +export class UACService { + isAvailable = false + + constructor ( + hostApp: HostAppService, + private electron: ElectronService, + ) { + this.isAvailable = hostApp.platform === Platform.Windows + && parseFloat(os.release()) >= 10 + && parseInt(os.release().split('.')[2]) >= 17692 + } + + patchSessionOptionsForUAC (sessionOptions: SessionOptions): SessionOptions { + let helperPath = path.join( + path.dirname(this.electron.app.getPath('exe')), + 'resources', + 'extras', + 'UAC.exe', + ) + + if (process.env.DEV) { + helperPath = path.join( + path.dirname(this.electron.app.getPath('exe')), + '..', '..', '..', + 'extras', + 'UAC.exe', + ) + } + + let options = { ...sessionOptions } + options.args = [options.command, ...options.args] + options.command = helperPath + return options + } + +} diff --git a/terminus-uac/.gitignore b/terminus-uac/.gitignore new file mode 100644 index 00000000..199ea6bc --- /dev/null +++ b/terminus-uac/.gitignore @@ -0,0 +1,4 @@ +Debug +*.ipch +*.suo +v15 \ No newline at end of file diff --git a/terminus-uac/UAC.sln b/terminus-uac/UAC.sln new file mode 100644 index 00000000..0997f1c3 --- /dev/null +++ b/terminus-uac/UAC.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2042 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UAC", "UAC\UAC.vcxproj", "{DCE3B955-DB20-4334-B11B-F6C040825A59}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x64.ActiveCfg = Debug|x64 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x64.Build.0 = Debug|x64 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x86.ActiveCfg = Debug|Win32 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x86.Build.0 = Debug|Win32 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x64.ActiveCfg = Release|x64 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x64.Build.0 = Release|x64 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x86.ActiveCfg = Release|Win32 + {DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77F43A07-B912-4CCD-9928-5C12F6D0112B} + EndGlobalSection +EndGlobal diff --git a/terminus-uac/UAC/UAC.cpp b/terminus-uac/UAC/UAC.cpp new file mode 100644 index 00000000..40afc3b3 Binary files /dev/null and b/terminus-uac/UAC/UAC.cpp differ diff --git a/terminus-uac/UAC/UAC.vcxproj b/terminus-uac/UAC/UAC.vcxproj new file mode 100644 index 00000000..34be090c --- /dev/null +++ b/terminus-uac/UAC/UAC.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {DCE3B955-DB20-4334-B11B-F6C040825A59} + Win32Proj + UAC + 10.0.18298.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/terminus-uac/UAC/UAC.vcxproj.filters b/terminus-uac/UAC/UAC.vcxproj.filters new file mode 100644 index 00000000..caa6fcac --- /dev/null +++ b/terminus-uac/UAC/UAC.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/terminus-uac/UAC/UAC.vcxproj.user b/terminus-uac/UAC/UAC.vcxproj.user new file mode 100644 index 00000000..39fdd36e --- /dev/null +++ b/terminus-uac/UAC/UAC.vcxproj.user @@ -0,0 +1,7 @@ + + + + cmd.exe + WindowsLocalDebugger + + \ No newline at end of file diff --git a/terminus-uac/UAC/app.manifest b/terminus-uac/UAC/app.manifest new file mode 100644 index 00000000..ff8f1687 --- /dev/null +++ b/terminus-uac/UAC/app.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/terminus-uac/UAC/stdafx.cpp b/terminus-uac/UAC/stdafx.cpp new file mode 100644 index 00000000..482796e2 Binary files /dev/null and b/terminus-uac/UAC/stdafx.cpp differ diff --git a/terminus-uac/UAC/stdafx.h b/terminus-uac/UAC/stdafx.h new file mode 100644 index 00000000..94d4ed87 Binary files /dev/null and b/terminus-uac/UAC/stdafx.h differ diff --git a/terminus-uac/UAC/targetver.h b/terminus-uac/UAC/targetver.h new file mode 100644 index 00000000..567cd346 Binary files /dev/null and b/terminus-uac/UAC/targetver.h differ