allow functions to call other functions #53

This commit is contained in:
undergroundwires
2021-01-16 13:26:41 +01:00
parent f1abd7682f
commit 7661575573
38 changed files with 1507 additions and 645 deletions

View File

@@ -0,0 +1,34 @@
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
import { generateIlCode, IILCode } from './ILCode';
export class ExpressionsCompiler implements IExpressionsCompiler {
public static readonly instance: IExpressionsCompiler = new ExpressionsCompiler();
protected constructor() { }
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters);
return intermediateCode.compile();
}
}
function substituteParameters(intermediateCode: IILCode, parameters: ParameterValueDictionary): IILCode {
const parameterNames = intermediateCode.getUniqueParameterNames();
ensureValuesProvided(parameterNames, parameters);
for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName];
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
}
return intermediateCode;
}
function ensureValuesProvided(names: string[], nameValues: ParameterValueDictionary) {
nameValues = nameValues || {};
const notProvidedNames = names.filter((name) => !Boolean(nameValues[name]));
if (notProvidedNames.length) {
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedNames)}`);
}
}
function printList(list: readonly string[]): string {
return `"${list.join('", "')}"`;
}

View File

@@ -0,0 +1,5 @@
export interface ParameterValueDictionary { [parameterName: string]: string; }
export interface IExpressionsCompiler {
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
}

View File

@@ -0,0 +1,114 @@
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
import { SharedFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
import { IFunctionCompiler } from './IFunctionCompiler';
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
export class FunctionCompiler implements IFunctionCompiler {
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
protected constructor(
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
}
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
const collection = new SharedFunctionCollection();
if (!functions || !functions.length) {
return collection;
}
ensureValidFunctions(functions);
functions
.filter((func) => hasCode(func))
.forEach((func) => {
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
collection.addFunction(shared);
});
functions
.filter((func) => hasCall(func))
.forEach((func) => {
const code = this.functionCallCompiler.compileCall(func.call, collection);
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
collection.addFunction(shared);
});
return collection;
}
}
function hasCode(data: FunctionData): boolean {
return Boolean(data.code);
}
function hasCall(data: FunctionData): boolean {
return Boolean(data.call);
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
ensureEitherCallOrCodeIsDefined(functions);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[]) {
// Ensure functions do not define both call and code
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
if (withBothCallAndCode.length) {
throw new Error(`both "code" and "call" are defined in ${printNames(withBothCallAndCode)}`);
}
// Ensure functions have either code or call
const hasEitherCodeOrCall = holders.filter((holder) => !hasCode(holder) && !hasCall(holder));
if (hasEitherCodeOrCall.length) {
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
}
}
function printNames(holders: readonly InstructionHolder[]) {
return printList(holders.map((holder) => holder.name));
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions
.map((func) => func.code)
.filter((code) => code),
);
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}

View File

@@ -0,0 +1,6 @@
import { FunctionData } from 'js-yaml-loader!*';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface IFunctionCompiler {
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
}

View File

@@ -0,0 +1,6 @@
export interface ISharedFunction {
readonly name: string;
readonly parameters?: readonly string[];
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,5 @@
import { ISharedFunction } from './ISharedFunction';
export interface ISharedFunctionCollection {
getFunctionByName(name: string): ISharedFunction;
}

View File

@@ -0,0 +1,14 @@
import { ISharedFunction } from './ISharedFunction';
export class SharedFunction implements ISharedFunction {
constructor(
public readonly name: string,
public readonly parameters: readonly string[],
public readonly code: string,
public readonly revertCode: string,
) {
if (!name) { throw new Error('undefined function name'); }
if (!code) { throw new Error(`undefined function ("${name}") code`); }
this.parameters = parameters || [];
}
}

View File

@@ -0,0 +1,23 @@
import { ISharedFunction } from './ISharedFunction';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export class SharedFunctionCollection implements ISharedFunctionCollection {
private readonly functionsByName = new Map<string, ISharedFunction>();
public addFunction(func: ISharedFunction): void {
if (!func) { throw new Error('undefined function'); }
if (this.functionsByName.has(func.name)) {
throw new Error(`function with name ${func.name} already exists`);
}
this.functionsByName.set(func.name, func);
}
public getFunctionByName(name: string): ISharedFunction {
if (!name) { throw Error('undefined function name'); }
const func = this.functionsByName.get(name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
}

View File

@@ -0,0 +1,88 @@
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
export class FunctionCallCompiler implements IFunctionCallCompiler {
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
protected constructor(
private readonly expressionsCompiler: IExpressionsCompiler = ExpressionsCompiler.instance) { }
public compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode {
if (!functions) { throw new Error('undefined functions'); }
if (!call) { throw new Error('undefined call'); }
const compiledCodes = new Array<ICompiledCode>();
const calls = getCallSequence(call);
calls.forEach((currentCall, currentCallIndex) => {
ensureValidCall(currentCall);
const commonFunction = functions.getFunctionByName(currentCall.function);
ensureExpectedParameters(commonFunction, currentCall);
let functionCode = compileCode(commonFunction, currentCall.parameters, this.expressionsCompiler);
if (currentCallIndex !== calls.length - 1) {
functionCode = appendLine(functionCode);
}
compiledCodes.push(functionCode);
});
const compiledCode = merge(compiledCodes);
return compiledCode;
}
}
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
if (!func.parameters && !call.parameters) {
return;
}
const unexpectedParameters = Object.keys(call.parameters || {})
.filter((callParam) => !func.parameters.includes(callParam));
if (unexpectedParameters.length) {
throw new Error(
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
}
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(
func: FunctionData,
parameters: FunctionCallParametersData,
compiler: IExpressionsCompiler): ICompiledCode {
return {
code: compiler.compileExpressions(func.code, parameters),
revertCode: compiler.compileExpressions(func.revertCode, parameters),
};
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
}
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}
function ensureValidCall(call: FunctionCallData) {
if (!call) {
throw new Error(`undefined function call`);
}
if (!call.function) {
throw new Error(`empty function name called`);
}
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}

View File

@@ -0,0 +1,4 @@
export interface ICompiledCode {
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,9 @@
import { ScriptFunctionCallData } from 'js-yaml-loader!*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
export interface IFunctionCallCompiler {
compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode;
}

View File

@@ -1,184 +1,42 @@
import { generateIlCode, IILCode } from './ILCode';
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode';
import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode';
interface ICompiledCode {
readonly code: string;
readonly revertCode: string;
}
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
import { IFunctionCompiler } from './Function/IFunctionCompiler';
import { FunctionCompiler } from './Function/FunctionCompiler';
export class ScriptCompiler implements IScriptCompiler {
private readonly functions: ISharedFunctionCollection;
constructor(
private readonly functions: readonly FunctionData[] | undefined,
private syntax: ILanguageSyntax) {
ensureValidFunctions(functions);
functions: readonly FunctionData[] | undefined,
private readonly syntax: ILanguageSyntax,
functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
) {
if (!syntax) { throw new Error('undefined syntax'); }
this.functions = functionCompiler.compileFunctions(functions);
}
public canCompile(script: ScriptData): boolean {
if (!script) { throw new Error('undefined script'); }
if (!script.call) {
return false;
}
return true;
}
public compile(script: ScriptData): IScriptCode {
this.ensureCompilable(script.call);
const compiledCodes = new Array<ICompiledCode>();
const calls = getCallSequence(script.call);
calls.forEach((currentCall, currentCallIndex) => {
ensureValidCall(currentCall, script.name);
const commonFunction = this.getFunctionByName(currentCall.function);
ensureExpectedParameters(commonFunction, currentCall);
let functionCode = compileCode(commonFunction, currentCall.parameters);
if (currentCallIndex !== calls.length - 1) {
functionCode = appendLine(functionCode);
}
compiledCodes.push(functionCode);
});
const scriptCode = merge(compiledCodes);
return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
}
private getFunctionByName(name: string): FunctionData {
const func = this.functions.find((f) => f.name === name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
private ensureCompilable(call: ScriptFunctionCallData) {
if (!this.functions || this.functions.length === 0) {
throw new Error('cannot compile without shared functions');
}
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
if (!script) { throw new Error('undefined script'); }
try {
const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
return new ScriptCode(
compiledCode.code,
compiledCode.revertCode,
this.syntax);
} catch (error) {
throw Error(`Script "${script.name}" ${error.message}`);
}
}
}
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
if (!func.parameters && !call.parameters) {
return;
}
const unexpectedParameters = Object.keys(call.parameters || {})
.filter((callParam) => !func.parameters.includes(callParam));
if (unexpectedParameters.length) {
throw new Error(
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
}
}
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions.map((func) => func.code));
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
if (!functions || functions.length === 0) {
return;
}
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(func: FunctionData, parameters: FunctionCallParametersData): ICompiledCode {
return {
code: compileExpressions(func.code, parameters),
revertCode: compileExpressions(func.revertCode, parameters),
};
}
function compileExpressions(code: string, parameters: FunctionCallParametersData): string {
let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters);
return intermediateCode.compile();
}
function substituteParameters(intermediateCode: IILCode, parameters: FunctionCallParametersData): IILCode {
const parameterNames = intermediateCode.getUniqueParameterNames();
if (parameterNames.length && !parameters) {
throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`);
}
for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName];
if (!parameterValue) {
throw Error(`parameter value is not provided for "${parameterName}" in function call`);
}
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
}
return intermediateCode;
}
function ensureValidCall(call: FunctionCallData, scriptName: string) {
if (!call) {
throw new Error(`undefined function call in script "${scriptName}"`);
}
if (!call.function) {
throw new Error(`empty function name called in script "${scriptName}"`);
}
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}

View File

@@ -31,7 +31,7 @@ function parseCode(script: ScriptData, context: ICategoryCollectionParseContext)
if (context.compiler.canCompile(script)) {
return context.compiler.compile(script);
}
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax);
return new ScriptCode(script.code, script.revertCode, context.syntax);
}
function ensureNotBothCallAndCode(script: ScriptData) {

View File

@@ -4,7 +4,7 @@ import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../Common/Enum';
import { generateIlCode } from './Script/Compiler/ILCode';
import { generateIlCode } from './Script/Compiler/Expressions/ILCode';
export function parseScriptingDefinition(
definition: ScriptingDefinitionData,

View File

@@ -18,30 +18,33 @@ declare module 'js-yaml-loader!*' {
readonly docs?: DocumentationUrlsData;
}
export interface FunctionData {
name: string;
code: string;
revertCode?: string;
parameters?: readonly string[];
export interface InstructionHolder {
readonly name: string;
readonly code?: string;
readonly revertCode?: string;
readonly call?: ScriptFunctionCallData;
}
export interface FunctionData extends InstructionHolder {
readonly parameters?: readonly string[];
}
export interface FunctionCallParametersData {
[index: string]: string;
readonly [index: string]: string;
}
export interface FunctionCallData {
function: string;
parameters?: FunctionCallParametersData;
readonly function: string;
readonly parameters?: FunctionCallParametersData;
}
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
export interface ScriptData extends DocumentableData {
name: string;
code?: string;
revertCode?: string;
call: ScriptFunctionCallData;
recommend?: string;
export interface ScriptData extends InstructionHolder, DocumentableData {
readonly name: string;
readonly recommend?: string;
}
export interface ScriptingDefinitionData {

View File

@@ -2719,8 +2719,19 @@ actions:
-
name: Disable NetBios for all interfaces
docs: https://10dsecurity.com/saying-goodbye-netbios/
code: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose}"
revertCode: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose}"
call:
function: RunPowerShell
parameters:
code:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose
}
revertCode:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose
}
-
category: Remove bloatware
children:
@@ -4168,64 +4179,72 @@ functions:
-
name: UninstallStoreApp
parameters: [ packageName ]
code: PowerShell -Command "Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage"
revertCode:
PowerShell -ExecutionPolicy Unrestricted -Command "
$package = Get-AppxPackage -AllUsers '{{ $packageName }}';
if (!$package) {
Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop
}
$manifest = $package.InstallLocation + '\AppxManifest.xml';
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\" "
call:
function: RunPowerShell
parameters:
code: Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage
revertCode:
$package = Get-AppxPackage -AllUsers '{{ $packageName }}';
if (!$package) {
Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop
}
$manifest = $package.InstallLocation + '\AppxManifest.xml';
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\"
-
name: UninstallSystemApp
parameters: [ packageName ]
# It simply renames files
# Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable)
# Otherwise they throw 0x80070032 when trying to uninstall them
code:
PowerShell -Command "
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
if (!$package) {
Write-Host 'Not installed';
exit 0;
}
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path $dir -Recurse -Force;
foreach($file in $files) {
if($file.Name.EndsWith('.OLD')) { continue; }
$newName = $file.FullName + '.OLD';
Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
}
};"
revertCode:
PowerShell -Command "
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
if (!$package) {
Write-Error 'App could not be found' -ErrorAction Stop;
}
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force;
foreach($file in $files) {
$newName = $file.FullName.Substring(0, $file.FullName.Length - 4);
Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
}
};"
call:
function: RunPowerShell
parameters:
code:
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
if (!$package) {
Write-Host 'Not installed';
exit 0;
}
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path $dir -Recurse -Force;
foreach($file in $files) {
if($file.Name.EndsWith('.OLD')) { continue; }
$newName = $file.FullName + '.OLD';
Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
}
}
revertCode:
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
if (!$package) {
Write-Error 'App could not be found' -ErrorAction Stop;
}
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force;
foreach($file in $files) {
$newName = $file.FullName.Substring(0, $file.FullName.Length - 4);
Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
}
}
-
name: UninstallCapability
parameters: [ capabilityName ]
code: PowerShell -Command "Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online"
revertCode: PowerShell -Command "$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*'; Add-WindowsCapability -Name \"$capability.Name\" -Online"
call:
function: RunPowerShell
parameters:
code: Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online
revertCode:
$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*';
Add-WindowsCapability -Name \"$capability.Name\" -Online
-
name: RenameSystemFile
parameters: [ filePath ]
@@ -4250,15 +4269,21 @@ functions:
-
name: SetVsCodeSetting
parameters: [ setting, powerShellValue ]
code:
Powershell -Command "
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
$json = Get-Content $jsonfile | Out-String | ConvertFrom-Json;
$json | Add-Member -Type NoteProperty -Name '{{ $setting }}' -Value {{ $powerShellValue }} -Force;
$json | ConvertTo-Json | Set-Content $jsonfile;"
revertCode:
Powershell -Command "
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
$json = Get-Content $jsonfile | ConvertFrom-Json;
$json.PSObject.Properties.Remove('{{ $setting }}');
$json | ConvertTo-Json | Set-Content $jsonfile;"
call:
function: RunPowerShell
parameters:
code:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
$json = Get-Content $jsonfile | Out-String | ConvertFrom-Json;
$json | Add-Member -Type NoteProperty -Name '{{ $setting }}' -Value {{ $powerShellValue }} -Force;
$json | ConvertTo-Json | Set-Content $jsonfile;
revertCode:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
$json = Get-Content $jsonfile | ConvertFrom-Json;
$json.PSObject.Properties.Remove('{{ $setting }}');
$json | ConvertTo-Json | Set-Content $jsonfile;
-
name: RunPowerShell
parameters: [ code, revertCode ]
code: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $code }}"
revertCode: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $revertCode }}"

View File

@@ -4,16 +4,17 @@ export class ScriptCode implements IScriptCode {
constructor(
public readonly execute: string,
public readonly revert: string,
scriptName: string,
syntax: ILanguageSyntax) {
if (!scriptName) { throw new Error('script name is undefined'); }
if (!syntax) { throw new Error('syntax is undefined'); }
validateCode(scriptName, execute, syntax);
if (!syntax) { throw new Error('undefined syntax'); }
validateCode(execute, syntax);
if (revert) {
scriptName = `${scriptName} (revert)`;
validateCode(scriptName, revert, syntax);
if (execute === revert) {
throw new Error(`${scriptName}: Code itself and its reverting code cannot be the same`);
try {
validateCode(revert, syntax);
if (execute === revert) {
throw new Error(`Code itself and its reverting code cannot be the same`);
}
} catch (err) {
throw Error(`(revert): ${err.message}`);
}
}
}
@@ -24,21 +25,21 @@ export interface ILanguageSyntax {
readonly commonCodeParts: string[];
}
function validateCode(name: string, code: string, syntax: ILanguageSyntax): void {
function validateCode(code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) {
throw new Error(`code of ${name} is empty or undefined`);
throw new Error(`code is empty or undefined`);
}
ensureNoEmptyLines(name, code);
ensureCodeHasUniqueLines(name, code, syntax);
ensureNoEmptyLines(code);
ensureCodeHasUniqueLines(code, syntax);
}
function ensureNoEmptyLines(name: string, code: string): void {
function ensureNoEmptyLines(code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`script has empty lines "${name}"`);
throw Error(`script has empty lines`);
}
}
function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageSyntax): void {
function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
const lines = code.split('\n')
.filter((line) => !shouldIgnoreLine(line, syntax));
if (lines.length === 0) {
@@ -46,7 +47,7 @@ function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageS
}
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
throw Error(`Duplicates detected in script :\n ${duplicateLines.join('\n')}`);
}
}