Skip to content

Commit fcb5149

Browse files
authored
Execution policy) Remote sign activate.ps1 script (#1414)
Resolves: #1341
1 parent ac36330 commit fcb5149

File tree

2 files changed

+146
-2
lines changed

2 files changed

+146
-2
lines changed

src/managers/common/utils.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,22 @@ export async function getShellActivationCommands(binDir: string): Promise<{
172172
shellDeactivation.set(ShellConstants.KSH, [{ executable: 'deactivate' }]);
173173

174174
if (await fs.pathExists(path.join(binDir, 'Activate.ps1'))) {
175-
shellActivation.set(ShellConstants.PWSH, [{ executable: '&', args: [path.join(binDir, `Activate.ps1`)] }]);
175+
shellActivation.set(ShellConstants.PWSH, [
176+
{
177+
executable: 'Set-ExecutionPolicy',
178+
args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'],
179+
},
180+
{ executable: '&', args: [path.join(binDir, `Activate.ps1`)] },
181+
]);
176182
shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]);
177183
} else if (await fs.pathExists(path.join(binDir, 'activate.ps1'))) {
178-
shellActivation.set(ShellConstants.PWSH, [{ executable: '&', args: [path.join(binDir, `activate.ps1`)] }]);
184+
shellActivation.set(ShellConstants.PWSH, [
185+
{
186+
executable: 'Set-ExecutionPolicy',
187+
args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'],
188+
},
189+
{ executable: '&', args: [path.join(binDir, `activate.ps1`)] },
190+
]);
179191
shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]);
180192
}
181193

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import assert from 'assert';
2+
import * as fs from 'fs-extra';
3+
import * as os from 'os';
4+
import path from 'path';
5+
import * as sinon from 'sinon';
6+
import * as platformUtils from '../../../common/utils/platformUtils';
7+
import { ShellConstants } from '../../../features/common/shellConstants';
8+
import { getShellActivationCommands } from '../../../managers/common/utils';
9+
10+
suite('getShellActivationCommands', () => {
11+
let isWindowsStub: sinon.SinonStub;
12+
let tmpDir: string;
13+
14+
setup(async () => {
15+
isWindowsStub = sinon.stub(platformUtils, 'isWindows');
16+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'venv-test-'));
17+
});
18+
19+
teardown(async () => {
20+
sinon.restore();
21+
await fs.remove(tmpDir);
22+
});
23+
24+
suite('PowerShell activation includes Set-ExecutionPolicy', () => {
25+
test('Activate.ps1 (capitalized) includes Set-ExecutionPolicy before activation', async () => {
26+
isWindowsStub.returns(true);
27+
await fs.writeFile(path.join(tmpDir, 'Activate.ps1'), '');
28+
29+
const result = await getShellActivationCommands(tmpDir);
30+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
31+
32+
assert.ok(pwshActivation, 'PowerShell activation should be defined');
33+
assert.strictEqual(pwshActivation.length, 2, 'Should have 2 commands: Set-ExecutionPolicy + activate');
34+
35+
// First command: Set-ExecutionPolicy
36+
assert.strictEqual(pwshActivation[0].executable, 'Set-ExecutionPolicy');
37+
assert.deepStrictEqual(pwshActivation[0].args, ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned']);
38+
39+
// Second command: & Activate.ps1
40+
assert.strictEqual(pwshActivation[1].executable, '&');
41+
assert.ok(pwshActivation[1].args);
42+
assert.strictEqual(pwshActivation[1].args.length, 1);
43+
assert.ok(
44+
pwshActivation[1].args[0].endsWith('Activate.ps1'),
45+
`Expected path ending with Activate.ps1, got: ${pwshActivation[1].args[0]}`,
46+
);
47+
});
48+
49+
test('activate.ps1 (lowercase) includes Set-ExecutionPolicy before activation', async () => {
50+
isWindowsStub.returns(true);
51+
// Only create lowercase variant
52+
await fs.writeFile(path.join(tmpDir, 'activate.ps1'), '');
53+
54+
const result = await getShellActivationCommands(tmpDir);
55+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
56+
57+
assert.ok(pwshActivation, 'PowerShell activation should be defined');
58+
assert.strictEqual(pwshActivation.length, 2, 'Should have 2 commands: Set-ExecutionPolicy + activate');
59+
60+
assert.strictEqual(pwshActivation[0].executable, 'Set-ExecutionPolicy');
61+
assert.deepStrictEqual(pwshActivation[0].args, ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned']);
62+
63+
assert.strictEqual(pwshActivation[1].executable, '&');
64+
assert.ok(pwshActivation[1].args);
65+
});
66+
67+
test('Set-ExecutionPolicy uses Process scope (session-only, no system-wide changes)', async () => {
68+
isWindowsStub.returns(true);
69+
await fs.writeFile(path.join(tmpDir, 'Activate.ps1'), '');
70+
71+
const result = await getShellActivationCommands(tmpDir);
72+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
73+
74+
assert.ok(pwshActivation);
75+
const policyArgs = pwshActivation[0].args;
76+
assert.ok(policyArgs);
77+
const scopeIndex = policyArgs.indexOf('-Scope');
78+
assert.ok(scopeIndex >= 0, 'Should have -Scope parameter');
79+
assert.strictEqual(policyArgs[scopeIndex + 1], 'Process', 'Scope must be Process');
80+
});
81+
});
82+
83+
suite('No PowerShell activation when Activate.ps1 is absent', () => {
84+
test('No pwsh activation when no ps1 file exists', async () => {
85+
isWindowsStub.returns(true);
86+
// Empty tmpDir — no ps1 files
87+
88+
const result = await getShellActivationCommands(tmpDir);
89+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
90+
91+
assert.strictEqual(pwshActivation, undefined, 'No PowerShell activation when no ps1 file exists');
92+
});
93+
});
94+
95+
suite('Other shells are not affected by execution policy change', () => {
96+
test('Bash activation does not include Set-ExecutionPolicy', async () => {
97+
isWindowsStub.returns(false);
98+
99+
const result = await getShellActivationCommands(tmpDir);
100+
const bashActivation = result.shellActivation.get(ShellConstants.BASH);
101+
102+
assert.ok(bashActivation, 'Bash activation should be defined');
103+
assert.strictEqual(bashActivation.length, 1, 'Bash should have only 1 command');
104+
assert.strictEqual(bashActivation[0].executable, 'source');
105+
});
106+
107+
test('CMD activation does not include Set-ExecutionPolicy', async () => {
108+
isWindowsStub.returns(true);
109+
await fs.writeFile(path.join(tmpDir, 'activate.bat'), '');
110+
111+
const result = await getShellActivationCommands(tmpDir);
112+
const cmdActivation = result.shellActivation.get(ShellConstants.CMD);
113+
114+
assert.ok(cmdActivation, 'CMD activation should be defined');
115+
assert.strictEqual(cmdActivation.length, 1, 'CMD should have only 1 command');
116+
assert.ok(cmdActivation[0].executable.endsWith('activate.bat'), 'CMD should use activate.bat');
117+
});
118+
});
119+
120+
suite('Windows unknown shell fallback', () => {
121+
test('Windows unknown shell uses activate without Set-ExecutionPolicy', async () => {
122+
isWindowsStub.returns(true);
123+
124+
const result = await getShellActivationCommands(tmpDir);
125+
const unknownActivation = result.shellActivation.get('unknown');
126+
127+
assert.ok(unknownActivation, 'Unknown shell activation should be defined');
128+
assert.strictEqual(unknownActivation.length, 1);
129+
assert.ok(unknownActivation[0].executable.endsWith('activate'));
130+
});
131+
});
132+
});

0 commit comments

Comments
 (0)