diff --git a/src/application/application.yaml b/src/application/application.yaml index ca24ce14..dcf27728 100644 --- a/src/application/application.yaml +++ b/src/application/application.yaml @@ -1267,6 +1267,32 @@ actions: docs: https://www.tenforums.com/tutorials/4077-turn-off-sync-settings-microsoft-account-windows-10-a.html code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\SettingSync\Groups\Language" /t REG_DWORD /v "Enabled" /d 0 /f revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\SettingSync\Groups\Language" /t REG_DWORD /v "Enabled" /d 1 /f + - + category: Prevent Microsoft family safety monitoring + children: + - + name: Disable Microsoft Family Safety Monitor + recommend: standard + code: |- + schtasks /change /disable /tn "Microsoft\Windows\Shell\FamilySafetyMonitor" + schtasks /change /disable /tn "Microsoft\Windows\Shell\FamilySafetyRefresh" + schtasks /change /disable /tn "Microsoft\Windows\Shell\FamilySafetyUpload" + revertCode: |- + schtasks /change /enable /tn "Microsoft\Windows\Shell\FamilySafetyMonitor" + schtasks /change /enable /tn "Microsoft\Windows\Shell\FamilySafetyRefresh" + schtasks /change /enable /tn "Microsoft\Windows\Shell\FamilySafetyUpload" + - + name: Uninstall Microsoft Family Safety Monitor + recommend: strict + call: + - + function: RenameSystemFile + parameters: + filePath: '%SystemRoot%\System32\WpcTok.exe' + - + function: RenameSystemFile + parameters: + filePath: '%SystemRoot%\System32\WpcMon.exe' - category: Configure programs children: @@ -4078,3 +4104,24 @@ functions: 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" + - + name: RenameSystemFile + parameters: [ filePath ] + code: |- + if exist "{{ $filePath }}" ( + takeown /f "{{ $filePath }}" + icacls "{{ $filePath }}" /grant administrators:F + move "{{ $filePath }}" "{{ $filePath }}.OLD" + echo Moved "{{ $filePath }}" to "{{ $filePath }}.OLD" + ) else ( + echo No action required: {{ $filePath }} is not found. + ) + revertCode: |- + if exist "{{ $filePath }}.OLD" ( + takeown /f "{{ $filePath }}.OLD" + icacls "{{ $filePath }}.OLD" /grant administrators:F + move "{{ $filePath }}.OLD" "{{ $filePath }}" + echo Moved "{{ $filePath }}.OLD" to "{{ $filePath }}" + ) else ( + echo Could not find backup file "{{ $filePath }}.OLD" 1>&2 + ) diff --git a/src/domain/ScriptCode.ts b/src/domain/ScriptCode.ts index 37c703dd..9d78d28f 100644 --- a/src/domain/ScriptCode.ts +++ b/src/domain/ScriptCode.ts @@ -23,19 +23,19 @@ function validateCode(name: string, code: string): void { if (!code || code.length === 0) { throw new Error(`code of ${name} is empty or undefined`); } - ensureCodeHasUniqueLines(name, code); ensureNoEmptyLines(name, code); + ensureCodeHasUniqueLines(name, code); } function ensureNoEmptyLines(name: string, 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 "${name}"`); } } function ensureCodeHasUniqueLines(name: string, code: string): void { const lines = code.split('\n') - .filter((line) => mayBeUniqueLine(line)); + .filter((line) => !shouldIgnoreLine(line)); if (lines.length === 0) { return; } @@ -45,13 +45,13 @@ function ensureCodeHasUniqueLines(name: string, code: string): void { } } -function mayBeUniqueLine(codeLine: string): boolean { - const trimmed = codeLine.trim(); - if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code - return false; - } - if (codeLine.startsWith(':: ') || codeLine.startsWith('REM ')) { // Is comment? - return false; - } - return true; +function shouldIgnoreLine(codeLine: string): boolean { + codeLine = codeLine.toLowerCase(); + const isCommentLine = () => codeLine.startsWith(':: ') || codeLine.startsWith('rem '); + const consistsOfFrequentCommands = () => { + const frequentCodeParts = [ '(', ')', 'else' ]; + const trimmed = codeLine.trim().split(' '); + return trimmed.every((part) => frequentCodeParts.includes(part)); + }; + return isCommentLine() || consistsOfFrequentCommands(); } diff --git a/tests/unit/domain/ScriptCode.spec.ts b/tests/unit/domain/ScriptCode.spec.ts index 5b4e02fd..b97f906d 100644 --- a/tests/unit/domain/ScriptCode.spec.ts +++ b/tests/unit/domain/ScriptCode.spec.ts @@ -1,6 +1,7 @@ import 'mocha'; import { expect } from 'chai'; import { ScriptCode } from '@/domain/ScriptCode'; +import { IScriptCode } from '../../../src/domain/IScriptCode'; describe('ScriptCode', () => { describe('scriptName', () => { @@ -15,75 +16,131 @@ describe('ScriptCode', () => { }); }); describe('code', () => { - it('cannot construct with duplicate lines', () => { + describe('throws with invalid code', () => { // arrange - const code = 'duplicate\nduplicate\ntest\nduplicate'; - // act - const act = () => createSut(code); - // assert - expect(act).to.throw(); + const scriptName = 'test-script'; + const testCases = [ + { + name: 'throws when "execute" and "revert" are same', + code: { + execute: 'same', + revert: 'same', + }, + expectedError: `${scriptName} (revert): Code itself and its reverting code cannot be the same`, + }, + { + name: 'cannot construct with undefined "execute"', + code: { + execute: undefined, + revert: 'code', + }, + expectedError: `code of ${scriptName} is empty or undefined`, + }, + { + name: 'cannot construct with empty "execute"', + code: { + execute: '', + revert: 'code', + }, + expectedError: `code of ${scriptName} is empty or undefined`, + }, + ]; + for (const testCase of testCases) { + it(testCase.name, () => { + // act + const act = () => new ScriptCode(scriptName, testCase.code.execute, testCase.code.revert); + // assert + expect(act).to.throw(testCase.expectedError); + }); + } }); - it('cannot construct with empty lines', () => { + describe('throws with invalid code in both "execute" or "revert"', () => { // arrange - const code = 'line1\n\n\nline2'; + const scriptName = 'script-name'; + const testCases = [ + { + testName: 'cannot construct with duplicate lines', + code: 'duplicate\nduplicate\ntest\nduplicate', + expectedMessage: 'Duplicates detected in script "$scriptName":\n duplicate\nduplicate', + }, + { + testName: 'cannot construct with empty lines', + code: 'line1\n\n\nline2', + expectedMessage: 'script has empty lines "$scriptName"', + }, + ]; // act - const act = () => createSut(code); + const actions = []; + for (const testCase of testCases) { + const substituteScriptName = (name) => testCase.expectedMessage.replace('$scriptName', name); + actions.push(...[ + { + act: () => new ScriptCode(scriptName, testCase.code, undefined), + testName: `execute: ${testCase.testName}`, + expectedMessage: substituteScriptName(scriptName), + }, + { + act: () => new ScriptCode(scriptName, 'valid code', testCase.code), + testName: `revert: ${testCase.testName}`, + expectedMessage: substituteScriptName(`${scriptName} (revert)`), + }, + ]); + } // assert - expect(act).to.throw(); + for (const action of actions) { + it(action.testName, () => { + expect(action.act).to.throw(action.expectedMessage, + `Code used: ${action.code}`); + }); + } }); - it('cannot construct with empty or undefined values', () => { + describe('sets as expected with valid "execute" or "revert"', () => { // arrange - const name = 'test-code'; - const errorMessage = `code of ${name} is empty or undefined`; - const invalidValues = [ '', undefined ]; - invalidValues.forEach((invalidValue) => { - // act - const act = () => new ScriptCode(name, invalidValue, ''); - // assert - expect(act).to.throw(errorMessage); - }); - }); - it('sets as expected', () => { - // arrange - const expected = 'expected-revert'; + const testCases = [ + { + testName: 'code is a valid string', + code: 'valid code', + }, + { + testName: 'code consists of frequent code parts', + code: ') else (', + }, + { + testName: 'code is a frequent code part', + code: ')', + }, + { + testName: 'code with duplicated comment lines (::)', + code: ':: comment\n:: comment', + }, + { + testName: 'code with duplicated comment lines (REM)', + code: 'REM comment\nREM comment', + }, + ]; // act - const sut = createSut(expected); + const actions = []; + for (const testCase of testCases) { + actions.push(...[ + { + testName: `execute: ${testCase.testName}`, + act: () => createSut(testCase.code), + expect: (sut: IScriptCode) => sut.execute === testCase.code, + }, + { + testName: `revert: ${testCase.testName}`, + act: () => createSut('different code', testCase.code), + expect: (sut: IScriptCode) => sut.revert === testCase.code, + }, + ]); + } // assert - expect(sut.execute).to.equal(expected); - }); - }); - describe('revert', () => { - it('cannot construct with duplicate lines', () => { - // arrange - const code = 'duplicate\nduplicate\ntest\nduplicate'; - // act - const act = () => createSut('REM', code); - // assert - expect(act).to.throw(); - }); - it('cannot construct with empty lines', () => { - // arrange - const code = 'line1\n\n\nline2'; - // act - const act = () => createSut('REM', code); - // assert - expect(act).to.throw(); - }); - it('cannot construct with when same as code', () => { - // arrange - const code = 'REM'; - // act - const act = () => createSut(code, code); - // assert - expect(act).to.throw(); - }); - it('sets as expected', () => { - // arrange - const expected = 'expected-revert'; - // act - const sut = createSut('abc', expected); - // assert - expect(sut.revert).to.equal(expected); + for (const action of actions) { + it(action.testName, () => { + const sut = action.act(); + expect(action.expect(sut)); + }); + } }); }); });