Skip to content

Commit 5941064

Browse files
CopiloteleanorjboydCopilot
committed
Fix: Restrict Set-ExecutionPolicy to Windows in PowerShell env activation (microsoft#1477)
fixes microsoft#1470 `Set-ExecutionPolicy` was unconditionally prepended to PowerShell activation commands, causing `Operation is not supported on this platform.` errors on macOS/Linux where the command is not available. ## Changes - **`src/managers/common/utils.ts`**: Extracted `buildPwshActivationCommands(ps1Path)` helper that only emits `Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned` when `isWindows()` is true. On non-Windows, only `& activate.ps1` is emitted. Also eliminates duplication between the `Activate.ps1`/`activate.ps1` branches. ```typescript // Before: always emitted on all platforms shellActivation.set(ShellConstants.PWSH, [ { executable: 'Set-ExecutionPolicy', args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'] }, { executable: '&', args: [path.join(binDir, 'Activate.ps1')] }, ]); // After: Set-ExecutionPolicy only on Windows function buildPwshActivationCommands(ps1Path: string): PythonCommandRunConfiguration[] { const commands: PythonCommandRunConfiguration[] = []; if (isWindows()) { commands.push({ executable: 'Set-ExecutionPolicy', args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'] }); } commands.push({ executable: '&', args: [ps1Path] }); return commands; } ``` - **`src/test/managers/common/utils.getShellActivationCommands.unit.test.ts`**: Adds tests asserting non-Windows PowerShell activation produces exactly one command (`& activate.ps1`) with no `Set-ExecutionPolicy`. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `https://api.github.com/graphql` > - Triggering command: `/usr/bin/gh gh issue list --state open --limit 5 --json number,title,labels` (http block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/microsoft/vscode-python-environments/settings/copilot/coding_agent) (admins only) > > </details> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 86d8342 commit 5941064

2 files changed

Lines changed: 50 additions & 14 deletions

File tree

src/managers/common/utils.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,18 @@ export function compareVersions(version1: string, version2: string): number {
139139
return 0;
140140
}
141141

142+
function buildPwshActivationCommands(ps1Path: string): PythonCommandRunConfiguration[] {
143+
const commands: PythonCommandRunConfiguration[] = [];
144+
if (isWindows()) {
145+
commands.push({
146+
executable: 'Set-ExecutionPolicy',
147+
args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'],
148+
});
149+
}
150+
commands.push({ executable: '&', args: [ps1Path] });
151+
return commands;
152+
}
153+
142154
export async function getShellActivationCommands(binDir: string): Promise<{
143155
shellActivation: Map<string, PythonCommandRunConfiguration[]>;
144156
shellDeactivation: Map<string, PythonCommandRunConfiguration[]>;
@@ -172,22 +184,10 @@ export async function getShellActivationCommands(binDir: string): Promise<{
172184
shellDeactivation.set(ShellConstants.KSH, [{ executable: 'deactivate' }]);
173185

174186
if (await fs.pathExists(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-
]);
187+
shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'Activate.ps1')));
182188
shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]);
183189
} else if (await fs.pathExists(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-
]);
190+
shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'activate.ps1')));
191191
shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]);
192192
}
193193

src/test/managers/common/utils.getShellActivationCommands.unit.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,42 @@ suite('getShellActivationCommands', () => {
8080
});
8181
});
8282

83+
suite('PowerShell activation on non-Windows omits Set-ExecutionPolicy', () => {
84+
test('Activate.ps1 (capitalized) on non-Windows has only the activation command', async () => {
85+
isWindowsStub.returns(false);
86+
await fs.writeFile(path.join(tmpDir, 'Activate.ps1'), '');
87+
88+
const result = await getShellActivationCommands(tmpDir);
89+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
90+
91+
assert.ok(pwshActivation, 'PowerShell activation should be defined');
92+
assert.strictEqual(pwshActivation.length, 1, 'Should have only 1 command: activate (no Set-ExecutionPolicy)');
93+
assert.strictEqual(pwshActivation[0].executable, '&');
94+
assert.ok(pwshActivation[0].args);
95+
assert.ok(
96+
pwshActivation[0].args[0].endsWith('Activate.ps1'),
97+
`Expected path ending with Activate.ps1, got: ${pwshActivation[0].args[0]}`,
98+
);
99+
});
100+
101+
test('activate.ps1 (lowercase) on non-Windows has only the activation command', async () => {
102+
isWindowsStub.returns(false);
103+
await fs.writeFile(path.join(tmpDir, 'activate.ps1'), '');
104+
105+
const result = await getShellActivationCommands(tmpDir);
106+
const pwshActivation = result.shellActivation.get(ShellConstants.PWSH);
107+
108+
assert.ok(pwshActivation, 'PowerShell activation should be defined');
109+
assert.strictEqual(pwshActivation.length, 1, 'Should have only 1 command: activate (no Set-ExecutionPolicy)');
110+
assert.strictEqual(pwshActivation[0].executable, '&');
111+
assert.ok(pwshActivation[0].args);
112+
assert.ok(
113+
pwshActivation[0].args[0].toLowerCase().endsWith('activate.ps1'),
114+
`Expected path ending with activate.ps1, got: ${pwshActivation[0].args[0]}`,
115+
);
116+
});
117+
});
118+
83119
suite('No PowerShell activation when Activate.ps1 is absent', () => {
84120
test('No pwsh activation when no ps1 file exists', async () => {
85121
isWindowsStub.returns(true);

0 commit comments

Comments
 (0)