From 6b2b64126ab0a9bfa4959dbd234ab6d8f8cb63e8 Mon Sep 17 00:00:00 2001 From: Pallavi Sah Date: Tue, 18 Mar 2025 17:27:05 -0700 Subject: [PATCH 1/7] Adding new test for admin password and admin usernames --- ...nPassword-Should-Not-Be-A-Literal.test.ps1 | 78 +++++++++++++++++++ ...nUsername-Should-Not-Be-A-Literal.test.ps1 | 60 ++++++++++++++ .../Fail/Literal-Admin-AdminPassword.json | 20 +++++ .../Pass/ExpressionValue.json | 18 +++++ ...Password-Should-Not-Be-A-Literal.tests.ps1 | 6 ++ .../Fail/Literal-Admin-AdminLogin.json | 20 +++++ 6 files changed, 202 insertions(+) create mode 100644 arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 create mode 100644 unit-tests/adminPassword-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminPassword.json create mode 100644 unit-tests/adminPassword-Should-Not-Be-A-Literal/Pass/ExpressionValue.json create mode 100644 unit-tests/adminPassword-Should-Not-Be-A-Literal/adminPassword-Should-Not-Be-A-Literal.tests.ps1 create mode 100644 unit-tests/adminUsername-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminLogin.json diff --git a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 new file mode 100644 index 00000000..d8c276ef --- /dev/null +++ b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 @@ -0,0 +1,78 @@ +<# +.Synopsis + Ensures that all adminUsernames are expressions +.Description + Ensures that all properties within a template named adminUsername are expressions, not literal strings +#> +param( + [Parameter(Mandatory = $true)] + [PSObject] + $TemplateObject +) + +# Find all references to an adminUserName +# Filtering the complete $TemplateObject directly fails with "The script failed due to call depth overflow." errors + +if ("resources" -in $TemplateObject.PSobject.Properties.Name) { + $adminPasswordRefsResources = $TemplateObject.resources | + Find-JsonContent -Key administratorLoginPassword -Value * -Like + + foreach ($ref in $adminPasswordRefsResources) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + $a = $ref.administratorLoginPassword + if ($a -isnot [string]) { + #check to see if this is a param value on a nested deployment - it will have a value property + if ($a.value -is [string]) { + $trimmedPassword = "$($a.value)".Trim() + } + else { + continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + } + } + else { + $trimmedPassword = "$($a)".Trim() + } + if ($trimmedPassword -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdministratorLoginPassword `"$trimmedPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error + continue # and move onto the next + } + } +} + +if ("variables" -in $TemplateObject.PSobject.Properties.Name) { + $adminPasswordRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key administratorLoginPassword -Value * -Like + + foreach ($ref in $adminPasswordRefsVariables) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + if ($ref.administratorLoginPassword -isnot [string]) { continue } + $trimmedPassword = "$($ref.administratorLoginPassword)".Trim() + if ($trimmedPassword -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error + continue # and move onto the next + } + } + + # TODO - irregular doesn't handle null gracefully so we need to test for it + if ($trimmedPassword -ne $null) { + $UserNameHasVariable = $trimmedPassword | ? -Extract + # this will return the outer most function in the expression + $userNameHasFunction = $trimmedPassword | ? -Extract + + # If we had a variable reference (not inside of another function) - then check it + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) + $variableValueExpression = $variableValue | ? + if (-not $variableValueExpression) { + Write-Error @" +AdminPassword references variable '$($UserNameHasVariable.variableName)', which has a literal value. +"@ -ErrorId AdminPassword.Is.Variable.Literal # write an error + } + } + } +} diff --git a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 index 1aa5abb6..23fa8d8d 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 @@ -17,6 +17,9 @@ if ("resources" -in $TemplateObject.PSobject.Properties.Name) { $adminUserNameRefsResources = $TemplateObject.resources | Find-JsonContent -Key adminUsername -Value * -Like + $adminLoginRefsResources = $TemplateObject.resources | + Find-JsonContent -Key administratorLogin -Value * -Like + foreach ($ref in $adminUserNameRefsResources) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. @@ -39,12 +42,38 @@ if ("resources" -in $TemplateObject.PSobject.Properties.Name) { continue # and move onto the next } } + + foreach ($ref in $adminLoginRefsResources) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + $a = $ref.administratorLogin + if ($a -isnot [string]) { + #check to see if this is a param value on a nested deployment - it will have a value property + if ($a.value -is [string]) { + $trimmedUserName = "$($a.value)".Trim() + } + else { + continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + } + } + else { + $trimmedUserName = "$($a)".Trim() + } + if ($trimmedUserName -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedUserName`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error + continue # and move onto the next + } + } } if ("variables" -in $TemplateObject.PSobject.Properties.Name) { $adminUserNameRefsVariables = $TemplateObject.variables | Find-JsonContent -Key adminUsername -Value * -Like + $administratorLoginRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key administratorLogin -Value * -Like + foreach ($ref in $adminUserNameRefsVariables) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. @@ -71,6 +100,37 @@ if ("variables" -in $TemplateObject.PSobject.Properties.Name) { if (-not $variableValueExpression) { Write-Error @" AdminUsername references variable '$($UserNameHasVariable.variableName)', which has a literal value. +"@ -ErrorId AdminUserName.Is.Variable.Literal # write an error + } + } + } + + foreach ($ref in $administratorLoginRefsVariables) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + if ($ref.administratorLogin -isnot [string]) { continue } + $trimmedUserName = "$($ref.administratorLogin)".Trim() + if ($trimmedUserName -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedUserName`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error + continue # and move onto the next + } + } + + # TODO - irregular doesn't handle null gracefully so we need to test for it + if ($trimmedUserName -ne $null) { + $UserNameHasVariable = $trimmedUserName | ? -Extract + # this will return the outer most function in the expression + $userNameHasFunction = $trimmedUserName | ? -Extract + + # If we had a variable reference (not inside of another function) - then check it + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) + $variableValueExpression = $variableValue | ? + if (-not $variableValueExpression) { + Write-Error @" +AdministratorLogin references variable '$($UserNameHasVariable.variableName)', which has a literal value. "@ -ErrorId AdminUserName.Is.Variable.Literal # write an error } } diff --git a/unit-tests/adminPassword-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminPassword.json b/unit-tests/adminPassword-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminPassword.json new file mode 100644 index 00000000..5d94d454 --- /dev/null +++ b/unit-tests/adminPassword-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminPassword.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "administratorLogin": "fixedusername", + "administratorLoginPassword": "fixedpassword" + }, + "resources": [ + { + "type": "Microsoft.DBforMySQL/flexibleServers", + "apiVersion": "2021-05-01", + "name": "name", + "location": "location", + "properties": { + "administratorLogin": "[variables('administratorLogin')]", + "administratorLoginPassword": "[variables('administratorLoginPassword')]" + } + } + ] +} \ No newline at end of file diff --git a/unit-tests/adminPassword-Should-Not-Be-A-Literal/Pass/ExpressionValue.json b/unit-tests/adminPassword-Should-Not-Be-A-Literal/Pass/ExpressionValue.json new file mode 100644 index 00000000..020a3ec8 --- /dev/null +++ b/unit-tests/adminPassword-Should-Not-Be-A-Literal/Pass/ExpressionValue.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "deploymentName": "Deployment-1.0" + }, + "resources": [ + { + "properties": { + "parameters": { + "adminPassword": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', variables('deploymentName')), '2019-10-01').outputs.accountSettings.value.accountAdminName]" + } + } + } + } + ] +} \ No newline at end of file diff --git a/unit-tests/adminPassword-Should-Not-Be-A-Literal/adminPassword-Should-Not-Be-A-Literal.tests.ps1 b/unit-tests/adminPassword-Should-Not-Be-A-Literal/adminPassword-Should-Not-Be-A-Literal.tests.ps1 new file mode 100644 index 00000000..d9e0b2ec --- /dev/null +++ b/unit-tests/adminPassword-Should-Not-Be-A-Literal/adminPassword-Should-Not-Be-A-Literal.tests.ps1 @@ -0,0 +1,6 @@ + +#requires -module arm-ttk +. $PSScriptRoot\..\arm-ttk.test.functions.ps1 +Test-TTK $psScriptRoot +return + diff --git a/unit-tests/adminUsername-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminLogin.json b/unit-tests/adminUsername-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminLogin.json new file mode 100644 index 00000000..5d94d454 --- /dev/null +++ b/unit-tests/adminUsername-Should-Not-Be-A-Literal/Fail/Literal-Admin-AdminLogin.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "variables": { + "administratorLogin": "fixedusername", + "administratorLoginPassword": "fixedpassword" + }, + "resources": [ + { + "type": "Microsoft.DBforMySQL/flexibleServers", + "apiVersion": "2021-05-01", + "name": "name", + "location": "location", + "properties": { + "administratorLogin": "[variables('administratorLogin')]", + "administratorLoginPassword": "[variables('administratorLoginPassword')]" + } + } + ] +} \ No newline at end of file From b369604169d7715495ce233d9b811cc89db1894d Mon Sep 17 00:00:00 2001 From: psah434 <114955590+psah434@users.noreply.github.com> Date: Tue, 18 Mar 2025 17:41:34 -0700 Subject: [PATCH 2/7] Update run-unit-tests.yml --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 92e2173a..def2bae3 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: InstallPester id: InstallPester shell: pwsh From 699a582548dc07db30e9760340c155c89baf1cfa Mon Sep 17 00:00:00 2001 From: psah434 <114955590+psah434@users.noreply.github.com> Date: Tue, 18 Mar 2025 17:42:41 -0700 Subject: [PATCH 3/7] Update run-unit-tests.yml --- .github/workflows/run-unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index def2bae3..4fef76fd 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -38,7 +38,7 @@ jobs: Write-Host "::debug:: RunPester $(@(foreach ($p in $Parameters.GetEnumerator()) {'-' + $p.Key + ' ' + $p.Value}) -join ' ')" & './arm-ttk/GitHubWorkflow/Steps/RunPester.ps1' @Parameters - name: PublishTestResults - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: PesterResults path: '**.TestResults.xml' From 23fe9f107193f397024fbdbfa51668a500f8dda1 Mon Sep 17 00:00:00 2001 From: Pallavi Sah Date: Thu, 20 Mar 2025 13:34:30 -0700 Subject: [PATCH 4/7] Addressing pr comments --- ...nPassword-Should-Not-Be-A-Literal.test.ps1 | 94 +++++++++++++++---- ...nUsername-Should-Not-Be-A-Literal.test.ps1 | 34 +++---- 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 index d8c276ef..71be288d 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 @@ -14,63 +14,123 @@ param( # Filtering the complete $TemplateObject directly fails with "The script failed due to call depth overflow." errors if ("resources" -in $TemplateObject.PSobject.Properties.Name) { - $adminPasswordRefsResources = $TemplateObject.resources | + $adminLoginPasswordRefsResources = $TemplateObject.resources | Find-JsonContent -Key administratorLoginPassword -Value * -Like - foreach ($ref in $adminPasswordRefsResources) { + foreach ($ref in $adminLoginPasswordRefsResources) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. $a = $ref.administratorLoginPassword if ($a -isnot [string]) { #check to see if this is a param value on a nested deployment - it will have a value property if ($a.value -is [string]) { - $trimmedPassword = "$($a.value)".Trim() + $trimmedAdminLoginPassword = "$($a.value)".Trim() + } + else { + continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + } + } + else { + $trimmedAdminLoginPassword = "$($a)".Trim() + } + if ($trimmedAdminLoginPassword -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdministratorLoginPassword `"$trimmedAdminLoginPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error + continue # and move onto the next + } + } + + $adminPasswordRefsResources = $TemplateObject.resources | + Find-JsonContent -Key adminPassword -Value * -Like + + foreach ($ref in $adminPasswordRefsResources) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + $a = $ref.adminPassword + if ($a -isnot [string]) { + #check to see if this is a param value on a nested deployment - it will have a value property + if ($a.value -is [string]) { + $trimmedAdminPassword = "$($a.value)".Trim() } else { continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) } } else { - $trimmedPassword = "$($a)".Trim() + $trimmedAdminPassword = "$($a)".Trim() } - if ($trimmedPassword -notmatch '\[[^\]]+\]') { + if ($trimmedAdminPassword -notmatch '\[[^\]]+\]') { # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLoginPassword `"$trimmedPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error + Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedAdminPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error continue # and move onto the next } } } if ("variables" -in $TemplateObject.PSobject.Properties.Name) { - $adminPasswordRefsVariables = $TemplateObject.variables | + $adminLoginPasswordRefsVariables = $TemplateObject.variables | Find-JsonContent -Key administratorLoginPassword -Value * -Like - foreach ($ref in $adminPasswordRefsVariables) { + foreach ($ref in $adminLoginPasswordRefsVariables) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. if ($ref.administratorLoginPassword -isnot [string]) { continue } - $trimmedPassword = "$($ref.administratorLoginPassword)".Trim() - if ($trimmedPassword -notmatch '\[[^\]]+\]') { + $trimmedAdminLoginPassword = "$($ref.administratorLoginPassword)".Trim() + if ($trimmedAdminLoginPassword -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminLoginPassword `"$trimmedAdminLoginPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error + continue # and move onto the next + } + } + + # TODO - irregular doesn't handle null gracefully so we need to test for it + if ($trimmedAdminLoginPassword -ne $null) { + $LoginPasswordHasVariable = $trimmedAdminLoginPassword | ? -Extract + # this will return the outer most function in the expression + $LoginPasswordHasFunction = $trimmedAdminLoginPassword | ? -Extract + + # If we had a variable reference (not inside of another function) - then check it + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) + $variableValueExpression = $variableValue | ? + if (-not $variableValueExpression) { + Write-Error @" +AdminLoginPassword references variable '$($LoginPasswordHasVariable.variableName)', which has a literal value. +"@ -ErrorId AdminPassword.Is.Variable.Literal # write an error + } + } + } + + $adminPasswordRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key adminPassword -Value * -Like + + foreach ($ref in $adminPasswordRefsVariables) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + if ($ref.adminPassword -isnot [string]) { continue } + $trimmedAdminPassword = "$($ref.adminPassword)".Trim() + if ($trimmedAdminPassword -notmatch '\[[^\]]+\]') { # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error + Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedAdminPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error continue # and move onto the next } } # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedPassword -ne $null) { - $UserNameHasVariable = $trimmedPassword | ? -Extract + if ($trimmedAdminPassword -ne $null) { + $LoginPasswordHasVariable = $trimmedAdminPassword | ? -Extract # this will return the outer most function in the expression - $userNameHasFunction = $trimmedPassword | ? -Extract + $LoginPasswordHasFunction = $trimmedAdminPassword | ? -Extract # If we had a variable reference (not inside of another function) - then check it # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username - if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { - $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) + if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) $variableValueExpression = $variableValue | ? if (-not $variableValueExpression) { Write-Error @" -AdminPassword references variable '$($UserNameHasVariable.variableName)', which has a literal value. +AdminPassword references variable '$($LoginPasswordHasVariable.variableName)', which has a literal value. "@ -ErrorId AdminPassword.Is.Variable.Literal # write an error } } diff --git a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 index 23fa8d8d..45b87c6c 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 @@ -17,9 +17,6 @@ if ("resources" -in $TemplateObject.PSobject.Properties.Name) { $adminUserNameRefsResources = $TemplateObject.resources | Find-JsonContent -Key adminUsername -Value * -Like - $adminLoginRefsResources = $TemplateObject.resources | - Find-JsonContent -Key administratorLogin -Value * -Like - foreach ($ref in $adminUserNameRefsResources) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. @@ -43,6 +40,10 @@ if ("resources" -in $TemplateObject.PSobject.Properties.Name) { } } + + $adminLoginRefsResources = $TemplateObject.resources | + Find-JsonContent -Key administratorLogin -Value * -Like + foreach ($ref in $adminLoginRefsResources) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. @@ -50,18 +51,18 @@ if ("resources" -in $TemplateObject.PSobject.Properties.Name) { if ($a -isnot [string]) { #check to see if this is a param value on a nested deployment - it will have a value property if ($a.value -is [string]) { - $trimmedUserName = "$($a.value)".Trim() + $trimmedAdminLogin = "$($a.value)".Trim() } else { continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) } } else { - $trimmedUserName = "$($a)".Trim() + $trimmedAdminLogin = "$($a)".Trim() } - if ($trimmedUserName -notmatch '\[[^\]]+\]') { + if ($trimmedAdminLogin -notmatch '\[[^\]]+\]') { # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedUserName`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error + Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedAdminLogin`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error continue # and move onto the next } } @@ -71,9 +72,6 @@ if ("variables" -in $TemplateObject.PSobject.Properties.Name) { $adminUserNameRefsVariables = $TemplateObject.variables | Find-JsonContent -Key adminUsername -Value * -Like - $administratorLoginRefsVariables = $TemplateObject.variables | - Find-JsonContent -Key administratorLogin -Value * -Like - foreach ($ref in $adminUserNameRefsVariables) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. @@ -105,23 +103,27 @@ AdminUsername references variable '$($UserNameHasVariable.variableName)', which } } + + $administratorLoginRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key administratorLogin -Value * -Like + foreach ($ref in $administratorLoginRefsVariables) { # Walk over each one # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. if ($ref.administratorLogin -isnot [string]) { continue } - $trimmedUserName = "$($ref.administratorLogin)".Trim() - if ($trimmedUserName -notmatch '\[[^\]]+\]') { + $trimmedAdminLogin = "$($ref.administratorLogin)".Trim() + if ($trimmedAdminLogin -notmatch '\[[^\]]+\]') { # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedUserName`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error + Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedAdminLogin`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error continue # and move onto the next } } # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedUserName -ne $null) { - $UserNameHasVariable = $trimmedUserName | ? -Extract + if ($trimmedAdminLogin -ne $null) { + $UserNameHasVariable = $trimmedAdminLogin | ? -Extract # this will return the outer most function in the expression - $userNameHasFunction = $trimmedUserName | ? -Extract + $userNameHasFunction = $trimmedAdminLogin | ? -Extract # If we had a variable reference (not inside of another function) - then check it # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username From 0e0ed863946b7449c8be1bb1d33f449b67a8e086 Mon Sep 17 00:00:00 2001 From: Pallavi Sah Date: Thu, 20 Mar 2025 14:17:46 -0700 Subject: [PATCH 5/7] Fixing comments --- .../adminPassword-Should-Not-Be-A-Literal.test.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 index 71be288d..15d5cb08 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 @@ -1,8 +1,8 @@ <# .Synopsis - Ensures that all adminUsernames are expressions + Ensures that all adminPasswords are expressions .Description - Ensures that all properties within a template named adminUsername are expressions, not literal strings + Ensures that all properties within a template named adminPassword are expressions, not literal strings #> param( [Parameter(Mandatory = $true)] @@ -10,7 +10,7 @@ param( $TemplateObject ) -# Find all references to an adminUserName +# Find all references to an adminPassword # Filtering the complete $TemplateObject directly fails with "The script failed due to call depth overflow." errors if ("resources" -in $TemplateObject.PSobject.Properties.Name) { @@ -90,7 +90,7 @@ if ("variables" -in $TemplateObject.PSobject.Properties.Name) { $LoginPasswordHasFunction = $trimmedAdminLoginPassword | ? -Extract # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic password if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) $variableValueExpression = $variableValue | ? @@ -124,7 +124,7 @@ AdminLoginPassword references variable '$($LoginPasswordHasVariable.variableName $LoginPasswordHasFunction = $trimmedAdminPassword | ? -Extract # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic password if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) $variableValueExpression = $variableValue | ? From c97da48053638f89e561b247eb018807593ea054 Mon Sep 17 00:00:00 2001 From: Pallavi Sah Date: Thu, 20 Mar 2025 14:52:05 -0700 Subject: [PATCH 6/7] Changing the latest module version --- arm-ttk/arm-ttk.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arm-ttk/arm-ttk.psd1 b/arm-ttk/arm-ttk.psd1 index ac41b0c7..850f7c95 100644 --- a/arm-ttk/arm-ttk.psd1 +++ b/arm-ttk/arm-ttk.psd1 @@ -1,5 +1,5 @@ @{ - ModuleVersion = 0.25 + ModuleVersion = 0.26 ModuleToProcess = 'arm-ttk.psm1' Description = 'Validation tools for Azure Resource Manager Templates' FormatsToProcess = 'arm-ttk.format.ps1xml' From 7c412378f7eedb58b59a7dfb7d2400bda1ba6996 Mon Sep 17 00:00:00 2001 From: Pallavi Sah Date: Tue, 25 Mar 2025 14:37:39 -0700 Subject: [PATCH 7/7] Addressing pr comments --- ...nPassword-Should-Not-Be-A-Literal.test.ps1 | 176 +++++++----------- ...nUsername-Should-Not-Be-A-Literal.test.ps1 | 174 ++++++----------- 2 files changed, 126 insertions(+), 224 deletions(-) diff --git a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 index 15d5cb08..7341302f 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminPassword-Should-Not-Be-A-Literal.test.ps1 @@ -12,127 +12,79 @@ param( # Find all references to an adminPassword # Filtering the complete $TemplateObject directly fails with "The script failed due to call depth overflow." errors - -if ("resources" -in $TemplateObject.PSobject.Properties.Name) { - $adminLoginPasswordRefsResources = $TemplateObject.resources | - Find-JsonContent -Key administratorLoginPassword -Value * -Like - - foreach ($ref in $adminLoginPasswordRefsResources) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - $a = $ref.administratorLoginPassword - if ($a -isnot [string]) { - #check to see if this is a param value on a nested deployment - it will have a value property - if ($a.value -is [string]) { - $trimmedAdminLoginPassword = "$($a.value)".Trim() +function Check-PasswordsInTemplate { + param ( + [Parameter(Mandatory = $true)] + [PSObject] + $TemplateObject, + [Parameter(Mandatory = $true)] + [string] + $AdminPwd + ) + if ("resources" -in $TemplateObject.PSobject.Properties.Name) { + $adminPwdRefsResources = $TemplateObject.resources | + Find-JsonContent -Key $AdminPwd -Value * -Like + + foreach ($ref in $adminPwdRefsResources) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + $a = $ref.$AdminPwd + if ($a -isnot [string]) { + #check to see if this is a param value on a nested deployment - it will have a value property + if ($a.value -is [string]) { + $trimmedPwd = "$($a.value)".Trim() + } + else { + continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + } } else { - continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) - } - } - else { - $trimmedAdminLoginPassword = "$($a)".Trim() - } - if ($trimmedAdminLoginPassword -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLoginPassword `"$trimmedAdminLoginPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error - continue # and move onto the next - } - } - - $adminPasswordRefsResources = $TemplateObject.resources | - Find-JsonContent -Key adminPassword -Value * -Like - - foreach ($ref in $adminPasswordRefsResources) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - $a = $ref.adminPassword - if ($a -isnot [string]) { - #check to see if this is a param value on a nested deployment - it will have a value property - if ($a.value -is [string]) { - $trimmedAdminPassword = "$($a.value)".Trim() + $trimmedPwd = "$($a)".Trim() } - else { - continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + if ($trimmedPwd -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedPwd`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error + continue # and move onto the next } } - else { - $trimmedAdminPassword = "$($a)".Trim() - } - if ($trimmedAdminPassword -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedAdminPassword`" is not an expression" -ErrorId AdminPassword.Is.Literal # write an error - continue # and move onto the next - } - } -} - -if ("variables" -in $TemplateObject.PSobject.Properties.Name) { - $adminLoginPasswordRefsVariables = $TemplateObject.variables | - Find-JsonContent -Key administratorLoginPassword -Value * -Like - - foreach ($ref in $adminLoginPasswordRefsVariables) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - if ($ref.administratorLoginPassword -isnot [string]) { continue } - $trimmedAdminLoginPassword = "$($ref.administratorLoginPassword)".Trim() - if ($trimmedAdminLoginPassword -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminLoginPassword `"$trimmedAdminLoginPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error - continue # and move onto the next - } } - - # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedAdminLoginPassword -ne $null) { - $LoginPasswordHasVariable = $trimmedAdminLoginPassword | ? -Extract - # this will return the outer most function in the expression - $LoginPasswordHasFunction = $trimmedAdminLoginPassword | ? -Extract - - # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic password - if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { - $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) - $variableValueExpression = $variableValue | ? - if (-not $variableValueExpression) { - Write-Error @" -AdminLoginPassword references variable '$($LoginPasswordHasVariable.variableName)', which has a literal value. -"@ -ErrorId AdminPassword.Is.Variable.Literal # write an error + + if ("variables" -in $TemplateObject.PSobject.Properties.Name) { + $adminPwdRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key $AdminPwd -Value * -Like + + foreach ($ref in $adminPwdRefsVariables) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + if ($ref.$AdminPwd -isnot [string]) { continue } + $trimmedPwd = "$($ref.$AdminPwd)".Trim() + if ($trimmedPwd -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedPwd`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error + continue # and move onto the next } } - } - - $adminPasswordRefsVariables = $TemplateObject.variables | - Find-JsonContent -Key adminPassword -Value * -Like - - foreach ($ref in $adminPasswordRefsVariables) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - if ($ref.adminPassword -isnot [string]) { continue } - $trimmedAdminPassword = "$($ref.adminPassword)".Trim() - if ($trimmedAdminPassword -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminPassword `"$trimmedAdminPassword`" is variable which is not an expression" -ErrorId AdminPassword.Var.Is.Literal # write an error - continue # and move onto the next - } - } - - # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedAdminPassword -ne $null) { - $LoginPasswordHasVariable = $trimmedAdminPassword | ? -Extract - # this will return the outer most function in the expression - $LoginPasswordHasFunction = $trimmedAdminPassword | ? -Extract - - # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic password - if ($LoginPasswordHasVariable -and $LoginPasswordHasFunction.FunctionName -eq 'variables') { - $variableValue = $TemplateObject.variables.($LoginPasswordHasVariable.VariableName) - $variableValueExpression = $variableValue | ? - if (-not $variableValueExpression) { - Write-Error @" -AdminPassword references variable '$($LoginPasswordHasVariable.variableName)', which has a literal value. -"@ -ErrorId AdminPassword.Is.Variable.Literal # write an error + + # TODO - irregular doesn't handle null gracefully so we need to test for it + if ($trimmedPwd -ne $null) { + $PwdHasVariable = $trimmedPwd | ? -Extract + # this will return the outer most function in the expression + $PwdHasFunction = $trimmedPwd | ? -Extract + + # If we had a variable reference (not inside of another function) - then check it + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic password + if ($PwdHasVariable -and $PwdHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($PwdHasVariable.VariableName) + $variableValueExpression = $variableValue | ? + if (-not $variableValueExpression) { + Write-Error "AdminPassword references variable '$($PwdHasVariable.variableName)', which has a literal value. " -ErrorId AdminPassword.Is.Variable.Literal # write an error + } } } } } + +$pwdValues = @("administratorLoginPassword", "adminPassword") +foreach ($pwdValue in $pwdValues) { + Check-PasswordsInTemplate -TemplateObject $TemplateObject -AdminPwd $pwdValue +} diff --git a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 index 45b87c6c..ef6801e0 100644 --- a/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 +++ b/arm-ttk/testcases/deploymentTemplate/adminUsername-Should-Not-Be-A-Literal.test.ps1 @@ -12,129 +12,79 @@ param( # Find all references to an adminUserName # Filtering the complete $TemplateObject directly fails with "The script failed due to call depth overflow." errors - -if ("resources" -in $TemplateObject.PSobject.Properties.Name) { - $adminUserNameRefsResources = $TemplateObject.resources | - Find-JsonContent -Key adminUsername -Value * -Like - - foreach ($ref in $adminUserNameRefsResources) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - $a = $ref.adminUsername - if ($a -isnot [string]) { - #check to see if this is a param value on a nested deployment - it will have a value property - if ($a.value -is [string]) { - $trimmedUserName = "$($a.value)".Trim() - } - else { - continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) - } - } - else { - $trimmedUserName = "$($a)".Trim() - } - if ($trimmedUserName -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminUsername `"$trimmedUserName`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error - continue # and move onto the next - } - } - +function Check-UsernamesInTemplate { + param ( + [Parameter(Mandatory = $true)] + [PSObject] + $TemplateObject, + [Parameter(Mandatory = $true)] + [string] + $AdminUsername + ) + if ("resources" -in $TemplateObject.PSobject.Properties.Name) { + $adminUserNameRefsResources = $TemplateObject.resources | + Find-JsonContent -Key $AdminUsername -Value * -Like - $adminLoginRefsResources = $TemplateObject.resources | - Find-JsonContent -Key administratorLogin -Value * -Like - - foreach ($ref in $adminLoginRefsResources) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - $a = $ref.administratorLogin - if ($a -isnot [string]) { - #check to see if this is a param value on a nested deployment - it will have a value property - if ($a.value -is [string]) { - $trimmedAdminLogin = "$($a.value)".Trim() + foreach ($ref in $adminUserNameRefsResources) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + $a = $ref.$AdminUsername + if ($a -isnot [string]) { + #check to see if this is a param value on a nested deployment - it will have a value property + if ($a.value -is [string]) { + $trimmedUserName = "$($a.value)".Trim() + } + else { + continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + } } else { - continue # since we don't know what object shape we're testing at this point (could be a param declaration on a nested deployment) + $trimmedUserName = "$($a)".Trim() } - } - else { - $trimmedAdminLogin = "$($a)".Trim() - } - if ($trimmedAdminLogin -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedAdminLogin`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error - continue # and move onto the next - } - } -} - -if ("variables" -in $TemplateObject.PSobject.Properties.Name) { - $adminUserNameRefsVariables = $TemplateObject.variables | - Find-JsonContent -Key adminUsername -Value * -Like - - foreach ($ref in $adminUserNameRefsVariables) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - if ($ref.adminUserName -isnot [string]) { continue } - $trimmedUserName = "$($ref.adminUserName)".Trim() - if ($trimmedUserName -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdminUsername `"$trimmedUserName`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error - continue # and move onto the next - } - } - - # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedUserName -ne $null) { - $UserNameHasVariable = $trimmedUserName | ? -Extract - # this will return the outer most function in the expression - $userNameHasFunction = $trimmedUserName | ? -Extract - - # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username - if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { - $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) - $variableValueExpression = $variableValue | ? - if (-not $variableValueExpression) { - Write-Error @" -AdminUsername references variable '$($UserNameHasVariable.variableName)', which has a literal value. -"@ -ErrorId AdminUserName.Is.Variable.Literal # write an error + if ($trimmedUserName -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminUsername `"$trimmedUserName`" is not an expression" -ErrorId AdminUsername.Is.Literal # write an error + continue # and move onto the next } } } - - $administratorLoginRefsVariables = $TemplateObject.variables | - Find-JsonContent -Key administratorLogin -Value * -Like - - foreach ($ref in $administratorLoginRefsVariables) { - # Walk over each one - # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. - if ($ref.administratorLogin -isnot [string]) { continue } - $trimmedAdminLogin = "$($ref.administratorLogin)".Trim() - if ($trimmedAdminLogin -notmatch '\[[^\]]+\]') { - # If they aren't expressions - Write-Error -TargetObject $ref -Message "AdministratorLogin `"$trimmedAdminLogin`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error - continue # and move onto the next + if ("variables" -in $TemplateObject.PSobject.Properties.Name) { + $adminUserNameRefsVariables = $TemplateObject.variables | + Find-JsonContent -Key $AdminUsername -Value * -Like + + foreach ($ref in $adminUserNameRefsVariables) { + # Walk over each one + # if the property is not a string, then it's likely a param value for a nested deployment, and we should skip it. + if ($ref.$AdminUsername -isnot [string]) { continue } + $trimmedUserName = "$($ref.$AdminUsername)".Trim() + if ($trimmedUserName -notmatch '\[[^\]]+\]') { + # If they aren't expressions + Write-Error -TargetObject $ref -Message "AdminUsername `"$trimmedUserName`" is variable which is not an expression" -ErrorId AdminUsername.Var.Is.Literal # write an error + continue # and move onto the next + } } - } - + # TODO - irregular doesn't handle null gracefully so we need to test for it - if ($trimmedAdminLogin -ne $null) { - $UserNameHasVariable = $trimmedAdminLogin | ? -Extract - # this will return the outer most function in the expression - $userNameHasFunction = $trimmedAdminLogin | ? -Extract - - # If we had a variable reference (not inside of another function) - then check it - # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username - if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { - $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) - $variableValueExpression = $variableValue | ? - if (-not $variableValueExpression) { - Write-Error @" -AdministratorLogin references variable '$($UserNameHasVariable.variableName)', which has a literal value. -"@ -ErrorId AdminUserName.Is.Variable.Literal # write an error + if ($trimmedUserName -ne $null) { + $UserNameHasVariable = $trimmedUserName | ? -Extract + # this will return the outer most function in the expression + $userNameHasFunction = $trimmedUserName | ? -Extract + + # If we had a variable reference (not inside of another function) - then check it + # TODO this will not flag things like concat so we should add a blacklist here to ensure it's still not a static or deterministic username + if ($UserNameHasVariable -and $userNameHasFunction.FunctionName -eq 'variables') { + $variableValue = $TemplateObject.variables.($UserNameHasVariable.VariableName) + $variableValueExpression = $variableValue | ? + if (-not $variableValueExpression) { + Write-Error "AdminUsername references variable '$($UserNameHasVariable.variableName)', which has a literal value. " -ErrorId AdminUserName.Is.Variable.Literal # write an error + } } } } } + +$usernameValues = @("administratorLogin", "adminUsername") +foreach ($usernameValue in $usernameValues) { + Check-UsernamesInTemplate -TemplateObject $TemplateObject -AdminUsername $usernameValue +}