Experimental UAC start-as-admin wrapper (fixes #511)

This commit is contained in:
Eugene Pankov
2018-12-24 18:11:26 +01:00
parent d7b305bf29
commit a7c1fe3425
19 changed files with 342 additions and 4 deletions

View File

@@ -21,6 +21,7 @@ export interface SessionOptions {
width?: number
height?: number
pauseAfterExit?: boolean
runAsAdministrator?: boolean
}
export interface Profile {

View File

@@ -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(

View File

@@ -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,
) {
}

View File

@@ -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

View File

@@ -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 () {

View File

@@ -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<Electron.MenuItemConstructorOptions[]> {
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 () {

View File

@@ -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(

View File

@@ -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
}
}