February 17, 2021·4 min read·engineeringterraformwindowsprovisioning

Terraform remote execution can explicitly target Windows over SSH

Terraform 0.14.7 separates an SSH connection from assumptions about the remote shell. The new Windows target changes how provisioner scripts are prepared and uploaded.

SSH tells a client how to reach another machine. It does not guarantee that the machine understands a Unix shell script. Terraform's remote execution code has to make further decisions about where to upload a script and how to prepare it for execution. Terraform 0.14.7, released on February 17, makes those assumptions configurable for a Windows target.

The new target_platform argument belongs in the connection block. Setting it to "windows" tells the SSH communicator that the remote environment requires Windows handling. Omitting it preserves "unix" as the default. This is a change to how Terraform prepares an operation over SSH, with a specific default assumption that the Windows SSH server uses cmd.exe.

I like Terraform's reusable abstraction over infrastructure APIs, but remote command execution exposes details that a resource declaration can otherwise hide. A transport and a command interpreter solve different problems. Making that distinction explicit in configuration is preferable to leaving the operator to infer it from a failed upload or an unexpected chmod command.

How the change developed

Henk Hofs's pull request 26865 began with an attempt to recognize a Windows destination through its path. The early implementation inspected a drive-letter prefix and used that observation to suppress Unix behavior. Subsequent revisions replaced the path inference with an explicit target_platform property in the connection information.

That progression explains the design better than the short release-note entry. A path is an output of the configuration, and users can override it. Inferring the remote execution environment from that path couples separate decisions. The final implementation names the platform directly, validates it against the two accepted values, and chooses a default script path from that setting.

The patch defines separate Unix and Windows script paths. For Unix, the default is /tmp/terraform_%RAND%.sh. The Windows default is C:/windows/temp/terraform_%RAND%.cmd. The extension of the latter agrees with the documented assumption about the remote Windows command interpreter. A destination path alone would not have expressed all of those expectations.

For an existing resource that already needs a connection, the relevant block can look like this:

connection {
  type            = "ssh"
  host            = self.public_ip
  user            = "Administrator"
  target_platform = "windows"
}

This is a connection-block excerpt, not a complete resource configuration. The surrounding resource must expose the referenced address, and the connection still needs the authentication appropriate to its environment. The new argument does not supply credentials or configure the remote SSH server.

The Unix operations that disappear

The implementation changes several parts of the SSH communicator under the same platform condition. It suppresses the pseudo-terminal request for a Windows target. When uploading a script, it no longer inserts the default Unix shebang. It also skips the command that would run chmod 0777 on the uploaded file.

Each operation encodes a Unix assumption independently of the command text supplied by the user. Rewriting an inline command into valid Windows syntax would not fix a Unix shebang prepended by Terraform. Nor would it make the remote command interpreter understand chmod. The setting has to affect the communicator's preparation steps as well as the chosen filename.

The changes remain conditional. Unix targets continue through the established code paths, and an empty platform value becomes unix. Existing Unix connections therefore do not need an extra declaration solely because Terraform introduces the setting. Invalid nonempty platform values produce an error instead of silently selecting a path.

A Windows target also does not mean every Windows shell has identical requirements. The documentation changes in the pull request distinguish cmd.exe from PowerShell. If the SSH server's default shell is PowerShell, the documented adjustment is a script_path ending in .ps1, such as c:/windows/temp/terraform_%RAND%.ps1. The target setting does not configure that server-side default.

This is the practical limit of the abstraction. Terraform can avoid adding Unix-specific preparation to a Windows script, but the author still has to write commands for the interpreter that will execute them. A module that contains shell commands carries an execution-environment dependency even when its infrastructure resources are portable.

The connection contract becomes more precise

The versioned changelog describes this enhancement as remote execution for a Windows command interpreter. The narrower implementation details explain who benefits. Configurations that reach Windows through SSH can declare that environment directly. This change should not be read as the introduction of an entirely new transport or as automatic conversion of existing Unix provisioner scripts.

I regard that precision as useful in a configuration language. Reusability depends on being able to state the assumptions a module makes. When a module includes remote execution, its required interpreter is part of those assumptions, just as its provider requirements are part of the infrastructure contract.

The release also repairs JSON presentation of module trees and provider requirements, according to the changelog. Those are separate corrections, but they share an emphasis on preserving information at an interface. For Windows remote execution, the missing information was the platform behind an SSH endpoint. Terraform 0.14.7 now carries that information into script preparation, where it selects the path and suppresses operations that the target cannot be assumed to support.