Skip to content
Open
18 changes: 11 additions & 7 deletions build_scripts/windows/scripts/az_msi.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
:: Copyright (C) Microsoft Corporation. All Rights Reserved.
::

@IF EXIST "%~dp0\..\python.exe" (
SET AZ_INSTALLER=MSI
"%~dp0\..\python.exe" -IBm azure.cli %*
) ELSE (
echo Failed to load python executable.
exit /b 1
)
@echo off
setlocal

IF NOT EXIST "%~dp0\..\python.exe" GOTO pynotfound
SET AZ_INSTALLER=MSI
"%~dp0\..\python.exe" -IBm azure.cli %*
GOTO end
:pynotfound
echo Failed to load python executable.
exit /b 1
:end
18 changes: 11 additions & 7 deletions build_scripts/windows/scripts/az_zip.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
:: Copyright (C) Microsoft Corporation. All Rights Reserved.
::

@IF EXIST "%~dp0\..\python.exe" (
SET AZ_INSTALLER=ZIP
"%~dp0\..\python.exe" -IBm azure.cli %*
) ELSE (
echo Failed to load python executable.
exit /b 1
)
@echo off
setlocal

IF NOT EXIST "%~dp0\..\python.exe" GOTO pynotfound
SET AZ_INSTALLER=ZIP
"%~dp0\..\python.exe" -IBm azure.cli %*
GOTO end
:pynotfound
echo Failed to load python executable.
exit /b 1
:end
11 changes: 6 additions & 5 deletions src/azure-cli/az.bat
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ setlocal
SET PYTHONPATH=%~dp0\src;%PYTHONPATH%
SET AZ_INSTALLER=PIP

IF EXIST "%~dp0\python.exe" (
"%~dp0\python.exe" -m azure.cli %*
) ELSE (
python -m azure.cli %*
)
IF NOT EXIST "%~dp0\python.exe" GOTO usepath
"%~dp0\python.exe" -m azure.cli %*
GOTO end
:usepath
python -m azure.cli %*
:end
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
_LINUX_ACCESS_EXT,
_WINDOWS_ACCESS_EXT,
_get_extension_instance_name,
_reset_windows_admin,
get_boot_log)
from azure.cli.command_modules.vm.custom import \
(attach_unmanaged_data_disk, detach_unmanaged_data_disk, get_vmss_instance_view)
Expand Down Expand Up @@ -166,6 +167,35 @@ def test_get_extension_instance_name_when_type_none(self):
# assert
self.assertEqual(result, 'extension-name')

@mock.patch('azure.cli.command_modules.vm.operations.vm_extension.VMExtensionCreate')
def test_reset_windows_admin_special_chars_in_password(self, mock_ext_create_cls):
"""Verify that passwords with shell metacharacters are passed verbatim via protected_settings."""
cmd = _get_test_cmd()

# Fake VM instance with minimal required keys
vm_instance = {
'location': 'westus',
'name': 'myvm',
'resources': [],
'instanceView': {},
}

special_passwords = ['Test)123', 'P@ss(word&1|2^3', 'abc)def(ghi']
for password in special_passwords:
mock_poller = mock.MagicMock()
mock_instance = mock.MagicMock(return_value=mock_poller)
mock_ext_create_cls.return_value = mock_instance

_reset_windows_admin(cmd, vm_instance, 'rg', 'AzureUser', password, no_wait=True)

# Verify the password was passed verbatim in protected_settings
kwargs = mock_instance.call_args.kwargs
command_args = kwargs['command_args'] if 'command_args' in kwargs \
else mock_instance.call_args.args[0]
protected = command_args['protected_settings']
self.assertEqual(protected, {'Password': password},
f"Password '{password}' was not passed verbatim in protected_settings")


class TestVMBootLog(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,22 @@ class VMImageListByAliasesScenarioTest(ScenarioTest):
def test_vm_image_list_by_alias(self):
result = self.cmd('vm image list --offer ubuntu').get_output_in_json()
self.assertTrue(len(result) >= 1)
self.assertEqual(result[-1]['publisher'], 'Canonical')
self.assertTrue('lts' in result[-1]['sku'])
canonical_results = [i for i in result if i['publisher'] == 'Canonical']
self.assertTrue(len(canonical_results) >= 1)
self.assertTrue(all(i['sku'] for i in canonical_results))

def test_vm_image_list_by_alias_and_filtered_by_arch(self):
result = self.cmd('vm image list --offer ubuntu --architecture x64').get_output_in_json()
self.assertTrue(len(result) >= 1)
self.assertEqual(result[-1]['publisher'], 'Canonical')
self.assertTrue('lts' in result[-1]['sku'])
self.assertEqual(result[-1]['architecture'], 'x64')
canonical_results = [i for i in result if i['publisher'] == 'Canonical']
self.assertTrue(len(canonical_results) >= 1)
self.assertTrue(all(i['sku'] for i in canonical_results))
self.assertTrue(all(i['architecture'] == 'x64' for i in result))


class VmReimageTest(ScenarioTest):

@unittest.skip('SubscriptionNotRegisteredForFeature: Microsoft.Network/AllowBringYourOwnPublicIpAddress')
@AllowLargeResponse()
@ResourceGroupPreparer(name_prefix='cli_test_vm_reimage_')
def test_vm_reimage(self, resource_group):
Expand Down Expand Up @@ -1989,6 +1992,28 @@ def test_vm_user_update_win(self, resource_group):

self.cmd('vm user update -g {rg} -n vm --username AzureUser --password testPassword1')

@live_only()
@AllowLargeResponse(size_kb=99999)
@ResourceGroupPreparer(name_prefix='cli_test_vm_user_update_win_sp_')
def test_vm_user_update_win_special_password(self, resource_group):
"""Regression test: passwords containing ')' must not break the Windows az.bat/az_msi.cmd/az_zip.cmd
launcher scripts, which previously used IF/ELSE block syntax that CMD interprets ')' as closing."""
self.kwargs.update({
'subnet': 'subnet1',
'vnet': 'vnet1',
'password_with_paren': 'testP@ss)word1'
})
self.cmd('vm create -g {rg} -n vm --image Win2022Datacenter --admin-username AzureUser '
'--admin-password testPassword0 --subnet {subnet} --vnet-name {vnet} --nsg-rule NONE '
'--size Standard_D2s_v3')

# Disable default outbound access
self.cmd(
'network vnet subnet update -g {rg} --vnet-name {vnet} -n {subnet} --default-outbound-access false')

# Verify that a password containing ')' is accepted without error
self.cmd('vm user update -g {rg} -n vm --username AzureUser --password {password_with_paren}')

@unittest.skip('SubscriptionNotRegisteredForFeature')
@AllowLargeResponse(size_kb=99999)
@ResourceGroupPreparer(name_prefix='cli_test_vm_size_properties')
Expand Down
Loading