"unrecognized arguments" when trying to pass in a string to AZ CLI provisioner cmdlet

Have an interesting problem which seems like a bug. When running the following privisioner AZ CLI cmdlet, it bombs while trying to pass in any string that has a space. The command works outside of TF (i.e.running cmd prompt). We’ve tried different work arounds such as variables, environment variable, or simply putting an escape\break (i.e. “jon doe”) but we keep getting the same error.

provisioner “local-exec” {
command = "az sql server ad-admin create -g ‘my-rg’ -s ‘my-sql’ -u ‘jon doe’ -i ‘acar5515-9555-4f3c-8df5-ed55555c55’

Error:
Error: Error running command 'az sql server ad-admin create --resource-group my-rg --server-name my-sql -u ‘jon doe’ --object-id ‘acar5515-9555-4f3c-8df5-ed55555c55’: exit status 2. Output: ERROR: az: error: unrecognized arguments: doe’usage: az [-h] [–verbose] [–debug]
[–output {json,jsonc,table,tsv,yaml,none}] [–query JMESPATH]
{sql} …

If you notice it fails right after the first name and says that “doe’” is an unrecognized argument. Again this works if you run it outside of Terraform but we cannot do anything to make it take the string in its entirety.

Appreciate any feedback!

Hi @bingerk!

Unfortunately on Windows the handling of command line quoting and escaping is quite tricky, because each application is responsible for handling its parsing itself and so each application can potentially use different rules for interpreting the given string of arguments.

As a consequence, Terraform follows the following sequence of steps in order to execute your given command:

  • It first takes your string and produces a command line argument array representing the command line:

    cmd /C "az sql server ad-admin create -g 'my-rg' -s 'my-sql' -u 'jon doe' -i 'acar5515-9555-4f3c-8df5-ed55555c55'"

  • To launch that command, Terraform uses the Windows CreateProcess API, passing that constructed command line as follows:

    CreateProcess("cmd", "/C \"az sql server ad-admin create -g 'my-rg' -s 'my-sql' -u 'jon doe' -i 'acar5515-9555-4f3c-8df5-ed55555c55'\"", ...)

  • The Windows command interpreter cmd.exe then gets to interpret that second string argument in whatever way it wants. I don’t know the internals of the command interpreter, but I believe it takes the quoted string given after /C and treats it in a similar way to if you’d typed that string at the Windows command prompt, which includes searching for special sequences like I/O redirection with >foo, etc. It’s presumably then calling CreateProcess itself, something like this:

    CreateProcess("az", "sql server ad-admin create -g 'my-rg' -s 'my-sql' -u 'jon doe' -i 'acar5515-9555-4f3c-8df5-ed55555c55'", ...)

  • At that point, it’s up to this az command to decide what to do with that string. It may or may not support using ' as a quoting character. If it’s doing its command line parsing using the C library argument parser or the CommandLineToArgvW API function – both of which are common choices – then it would not support ' as a quoting character and would require you to use " instead.

With all of those details aside, what I’d try next is to use " instead of ' as the quoting character, which would therefore be supported by a program parsing the command line in the standard way on Windows. Unfortunately that does require some escaping in Terraform:

  command = "az sql server ad-admin create -g \"my-rg\" -s \"my-sql\" -u \"jon doe\" -i \"acar5515-9555-4f3c-8df5-ed55555c55\""

If this fixes it, then of course it will raise the question of why this was working for you when you ran it directly from the Windows command prompt. My best guess for that would be that you were typing the command into PowerShell rather than into cmd.exe, and so PowerShell was doing its own pre-processing of the arguments before passing them to CreateProcess internally.

If I recall correctly, PowerShell follows the following procedure for launching executables (as opposed to its own cmdlets):

  • Parse the command line into a sequence of strings using PowerShell’s own quoting rules, which do support ' as a quoting character and would thus produce a sequence like this from your input:

    ["az", "sql", "server", "ad-admin", "create", "-g", "my-rg", "-s", "my-sql", "-u", "jon doe", "-i", "acar5515-9555-4f3c-8df5-ed55555c55"]

  • In order to use this with CreateProcess it must turn everything except the first argument into a single string, which it does by joining them all with spaces and adding quotes around any item that already has a space in it:

    CreateProcess("az", "sql server ad-admin create -g my-rg -s my-sql -u \"jon doe\" -i acar5515-9555-4f3c-8df5-ed55555c55")

  • This time, the command line string received by the az program would be the following:

    sql server ad-admin create -g my-rg -s my-sql -u "jon doe" -i acar5515-9555-4f3c-8df5-ed55555c55

    …and so jon doe is now in double quotes as the standard argument parser expects, allowing it to work.

If the above explains the difference, then another option available to you is to ask Terraform to run the command using powershell.exe instead of cmd.exe. I’m not sure exactly how that would be done because I’ve never really used Powershell, but based on the powershell.exe documentation I would expect something like this to work:

  provisioner "local-exec" {
    interpreter = ["powershell.exe", "-Command"]
    command     = "az sql server ad-admin create -g 'my-rg' -s 'my-sql' -u 'jon doe' -i 'acar5515-9555-4f3c-8df5-ed55555c55'"
  }

The interpreter argument overrides Terraform’s default of using cmd /C "..." to run the command you provided, making Terraform use powershell.exe -Command "..." instead.


I’m sorry there’s so much complexity here! The design of command line argument handling on Windows prevents there from being a straightforward answer to this question, but hopefully the above gives you some new things to try and some ideas as to why things seem to be behaving differently at the command line directly vs. in Terraform.

This sort of complexity is why Terraform provisioners are a last resort; if there’s any way to do that ad-admin create operation using a real resource type in the azurerm provider then that’d make this a lot less messy to achieve.

I don’t know what that command does, so I don’t know if any such resource type exists, but if not then the Azure provider team might be open to adding it if it’s calling into a normal Azure API underneath.

Wow, that is some great detail! Most of it makes sense and the only concluding points I might make is

  1. I did try using the “jon doe” but it still failed.

  2. I did originally run it from PowerShell but then for a test I just did it via cmd.exe and it worked from there as well

  3. Ultimately, my work around was to just use powershell (e.g.
    Set-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName “my-rg” -ServerName “my-sql” -DisplayName “Jon Doe” -ObjectID “acar5515-9555-4f3c-8df5-ed55555c55”) and skip AZ CLI for this specific use case.

I know these provisioners are not ideal, but kind of stuck for a few settings we need to make (such as the aforementioned use case).

Thank you for your excellent and quick feedback!

I’m sorry that it didn’t help! It’s often challenging to puzzle out the full sequence of parsing and re-stringing that happens when running command lines on Windows, but at least your answer of using PowerShell directly avoids all of that by only having to interact with PowerShell’s parser! :smiley: