Skip to content

Commit e0fc7d1

Browse files
authored
[Identity] Check exit code for PowershellCredential (#34271)
Fixed an issue in AzurePowerShellCredential where if pwsh isn't available and the Command Prompt language is not English, it would not fall back to powershell. Signed-off-by: Paul Van Eck <[email protected]>
1 parent 02d8be6 commit e0fc7d1

File tree

5 files changed

+43
-15
lines changed

5 files changed

+43
-15
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fixed an issue in `AzurePowerShellCredential` where if `pwsh` isn't available and the Command Prompt language is not English, it would not fall back to `powershell`. ([#34271](https://github.com/Azure/azure-sdk-for-python/pull/34271))
12+
1113
### Other Changes
1214

1315
## 1.16.0b1 (2024-02-06)

sdk/identity/azure-identity/azure/identity/_credentials/azure_powershell.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def run_command_line(command_line: List[str], timeout: int) -> str:
135135
try:
136136
proc = start_process(command_line)
137137
stdout, stderr = proc.communicate(**kwargs)
138-
if sys.platform.startswith("win") and "' is not recognized" in stderr:
138+
if sys.platform.startswith("win") and ("' is not recognized" in stderr or proc.returncode == 9009):
139139
# pwsh.exe isn't on the path; try powershell.exe
140140
command_line[-1] = command_line[-1].replace("pwsh", "powershell", 1)
141141
proc = start_process(command_line)
@@ -192,7 +192,7 @@ def get_command_line(scopes: Tuple[str, ...], tenant_id: str) -> List[str]:
192192

193193
command = "pwsh -NoProfile -NonInteractive -EncodedCommand " + encoded_script
194194
if sys.platform.startswith("win"):
195-
return ["cmd", "/c", command]
195+
return ["cmd", "/c", command + " & exit"]
196196
return ["/bin/sh", "-c", command]
197197

198198

sdk/identity/azure-identity/azure/identity/aio/_credentials/azure_powershell.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async def run_command_line(command_line: List[str], timeout: int) -> str:
108108
try:
109109
proc = await start_process(command_line)
110110
stdout, stderr = await asyncio.wait_for(proc.communicate(), 10)
111-
if sys.platform.startswith("win") and b"' is not recognized" in stderr:
111+
if sys.platform.startswith("win") and (b"' is not recognized" in stderr or proc.returncode == 9009):
112112
# pwsh.exe isn't on the path; try powershell.exe
113113
command_line[-1] = command_line[-1].replace("pwsh", "powershell", 1)
114114
proc = await start_process(command_line)

sdk/identity/azure-identity/tests/test_powershell_credential.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def test_get_token(stderr):
110110
command = args[0][-1]
111111
assert command.startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
112112

113-
encoded_script = command.split()[-1]
113+
match = re.search(r"-EncodedCommand\s+(\S+)", command)
114+
assert match, "couldn't find encoded script in command line"
115+
encoded_script = match.groups()[0]
114116
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
115117
assert "TenantId" not in decoded_script
116118
assert "Get-AzAccessToken -ResourceUrl '{}'".format(scope) in decoded_script
@@ -250,7 +252,14 @@ def emit(self, record):
250252
assert False, "Credential should have included stderr in a DEBUG level message"
251253

252254

253-
def test_windows_powershell_fallback():
255+
@pytest.mark.parametrize(
256+
"error_message",
257+
(
258+
"'pwsh' is not recognized as an internal or external command,\r\noperable program or batch file.",
259+
"some other message",
260+
),
261+
)
262+
def test_windows_powershell_fallback(error_message):
254263
"""On Windows, the credential should fall back to powershell.exe when pwsh.exe isn't on the path"""
255264

256265
class Fake:
@@ -262,8 +271,8 @@ def Popen(args, **kwargs):
262271
if args[-1].startswith("pwsh"):
263272
assert Fake.calls == 1, 'credential should invoke "pwsh" only once'
264273
stdout = ""
265-
stderr = "'pwsh' is not recognized as an internal or external command,\r\noperable program or batch file."
266-
return_code = 1
274+
stderr = error_message
275+
return_code = 9009
267276
else:
268277
assert args[-1].startswith("powershell"), 'credential should fall back to "powershell"'
269278
stdout = NO_AZ_ACCOUNT_MODULE
@@ -288,7 +297,9 @@ def test_multitenant_authentication():
288297

289298
def fake_Popen(command, **_):
290299
assert command[-1].startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
291-
encoded_script = command[-1].split()[-1]
300+
match = re.search(r"-EncodedCommand\s+(\S+)", command[-1])
301+
assert match, "couldn't find encoded script in command line"
302+
encoded_script = match.groups()[0]
292303
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
293304
match = re.search(r"Get-AzAccessToken -ResourceUrl '(\S+)'(?: -TenantId (\S+))?", decoded_script)
294305
tenant = match.groups()[1]
@@ -318,7 +329,9 @@ def test_multitenant_authentication_not_allowed():
318329

319330
def fake_Popen(command, **_):
320331
assert command[-1].startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
321-
encoded_script = command[-1].split()[-1]
332+
match = re.search(r"-EncodedCommand\s+(\S+)", command[-1])
333+
assert match, "couldn't find encoded script in command line"
334+
encoded_script = match.groups()[0]
322335
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
323336
match = re.search(r"Get-AzAccessToken -ResourceUrl '(\S+)'(?: -TenantId (\S+))?", decoded_script)
324337
tenant = match.groups()[1]

sdk/identity/azure-identity/tests/test_powershell_credential_async.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ async def test_get_token(stderr):
100100
command = args[-1]
101101
assert command.startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
102102

103-
encoded_script = command.split()[-1]
103+
match = re.search(r"-EncodedCommand\s+(\S+)", command)
104+
assert match, "couldn't find encoded script in command line"
105+
encoded_script = match.groups()[0]
104106
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
105107
assert "TenantId" not in decoded_script
106108
assert "Get-AzAccessToken -ResourceUrl '{}'".format(scope) in decoded_script
@@ -247,7 +249,14 @@ async def test_windows_event_loop():
247249

248250

249251
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="tests Windows-specific behavior")
250-
async def test_windows_powershell_fallback():
252+
@pytest.mark.parametrize(
253+
"error_message",
254+
(
255+
"'pwsh' is not recognized as an internal or external command,\r\noperable program or batch file.",
256+
"some other message",
257+
),
258+
)
259+
async def test_windows_powershell_fallback(error_message):
251260
"""On Windows, the credential should fall back to powershell.exe when pwsh.exe isn't on the path"""
252261

253262
calls = 0
@@ -259,8 +268,8 @@ async def mock_exec(*args, **kwargs):
259268
if args[-1].startswith("pwsh"):
260269
assert calls == 1, 'credential should invoke "pwsh" only once'
261270
stdout = ""
262-
stderr = "'pwsh' is not recognized as an internal or external command,\r\noperable program or batch file."
263-
return_code = 1
271+
stderr = error_message
272+
return_code = 9009
264273
else:
265274
assert args[-1].startswith("powershell"), 'credential should fall back to "powershell"'
266275
stdout = NO_AZ_ACCOUNT_MODULE
@@ -286,7 +295,9 @@ async def test_multitenant_authentication():
286295
async def fake_exec(*args, **_):
287296
command = args[2]
288297
assert command.startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
289-
encoded_script = command.split()[-1]
298+
match = re.search(r"-EncodedCommand\s+(\S+)", command)
299+
assert match, "couldn't find encoded script in command line"
300+
encoded_script = match.groups()[0]
290301
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
291302
match = re.search(r"Get-AzAccessToken -ResourceUrl '(\S+)'(?: -TenantId (\S+))?", decoded_script)
292303
tenant = match[2]
@@ -317,7 +328,9 @@ async def test_multitenant_authentication_not_allowed():
317328
async def fake_exec(*args, **_):
318329
command = args[2]
319330
assert command.startswith("pwsh -NoProfile -NonInteractive -EncodedCommand ")
320-
encoded_script = command.split()[-1]
331+
match = re.search(r"-EncodedCommand\s+(\S+)", command)
332+
assert match, "couldn't find encoded script in command line"
333+
encoded_script = match.groups()[0]
321334
decoded_script = base64.b64decode(encoded_script).decode("utf-16-le")
322335
match = re.search(r"Get-AzAccessToken -ResourceUrl '(\S+)'(?: -TenantId (\S+))?", decoded_script)
323336
tenant = match[2]

0 commit comments

Comments
 (0)