diff --git a/examples/templates/azure-windows/FirstLogonCommands.xml b/examples/templates/azure-windows/FirstLogonCommands.xml
new file mode 100644
index 0000000000000..ac4a9d80796c0
--- /dev/null
+++ b/examples/templates/azure-windows/FirstLogonCommands.xml
@@ -0,0 +1,12 @@
+
+
+ cmd /c "copy C:\AzureData\CustomData.bin C:\AzureData\Initialize.ps1"
+ Copy Initialize.ps1 to file from CustomData
+ 3
+
+
+ powershell.exe -sta -ExecutionPolicy Unrestricted -Command "C:\AzureData\Initialize.ps1 *> C:\AzureData\Initialize.log"
+ Execute Initialize.ps1 script
+ 4
+
+
diff --git a/examples/templates/azure-windows/Initialize.ps1.tftpl b/examples/templates/azure-windows/Initialize.ps1.tftpl
new file mode 100644
index 0000000000000..5a195f589ec66
--- /dev/null
+++ b/examples/templates/azure-windows/Initialize.ps1.tftpl
@@ -0,0 +1,73 @@
+# This script gets run once when the VM is first created.
+
+# Initialize the data disk & home directory.
+$disk = Get-Disk -Number 2
+if ($disk.PartitionStyle -Eq 'RAW')
+{
+ "Initializing data disk"
+ $disk | Initialize-Disk
+} else {
+ "data disk already initialized"
+}
+
+$partitions = Get-Partition -DiskNumber $disk.Number | Where-Object Type -Ne 'Reserved'
+if ($partitions.Count -Eq 0) {
+ "Creating partition on data disk"
+ $partition = New-Partition -DiskNumber $disk.Number -UseMaximumSize
+} else {
+ $partition = $partitions[0]
+ $s = "data disk already has partition of size {0:n1} GiB" -f ($partition.Size / 1073741824)
+ Write-Output $s
+}
+
+$volume = Get-Volume -Partition $partition
+if ($volume.FileSystemType -Eq 'Unknown')
+{
+ "Formatting data disk"
+ Format-Volume -InputObject $volume -FileSystem NTFS -Confirm:$false
+} else {
+ "data disk is already formatted"
+}
+
+# Mount the partition
+Add-PartitionAccessPath -InputObject $partition -AccessPath "F:"
+
+# Enable RDP
+Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0
+# Enable RDP through Windows Firewall
+Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
+# Disable Network Level Authentication (NLA)
+# Clients will connect via Coder's tunnel
+(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -ComputerName $env:COMPUTERNAME -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0)
+
+# Install Chocolatey package manager
+Set-ExecutionPolicy Bypass -Scope Process -Force
+[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
+iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+# Reload path so sessions include "choco" and "refreshenv"
+$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+
+# Install Git and reload path
+choco install -y git
+$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
+
+# Set protocol to TLS1.2 for agent download
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+# Set Coder Agent to run immediately, and on each restart
+$init_script = @'
+${init_script}
+'@
+Out-File -FilePath "C:\AzureData\CoderAgent.ps1" -InputObject $init_script
+$task = @{
+ TaskName = 'CoderAgent'
+ Action = (New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-sta -ExecutionPolicy Unrestricted -Command "C:\AzureData\CoderAgent.ps1 *>> C:\AzureData\CoderAgent.log"')
+ Trigger = (New-ScheduledTaskTrigger -AtStartup), (New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(15))
+ Settings = (New-ScheduledTaskSettingsSet -DontStopOnIdleEnd -ExecutionTimeLimit ([TimeSpan]::FromDays(3650)) -Compatibility Win8)
+ Principal = (New-ScheduledTaskPrincipal -UserId 'vm\coder' -RunLevel Highest -LogonType S4U)
+}
+Register-ScheduledTask @task -Force
+
+# Additional Chocolatey package installs (optional, uncomment to enable)
+# choco feature enable -n=allowGlobalConfirmation
+# choco install visualstudio2022community --package-parameters "--add=Microsoft.VisualStudio.Workload.ManagedDesktop;includeRecommended --passive --locale en-US"
diff --git a/examples/templates/azure-windows/README.md b/examples/templates/azure-windows/README.md
new file mode 100644
index 0000000000000..768e5f5e9ed79
--- /dev/null
+++ b/examples/templates/azure-windows/README.md
@@ -0,0 +1,23 @@
+---
+name: Develop in Windows on Azure
+description: Get started with Windows development on Microsoft Azure.
+tags: [cloud, azure, windows]
+icon: /icon/azure.png
+---
+
+# azure-windows
+
+To get started, run `coder templates init`. When prompted, select this template.
+Follow the on-screen instructions to proceed.
+
+## Authentication
+
+This template assumes that coderd is run in an environment that is authenticated
+with Azure. For example, run `az login` then `az account set --subscription=`
+to import credentials on the system and user running coderd. For other ways to
+authenticate [consult the Terraform docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure).
+
+## Dependencies
+
+This template depends on the Azure CLI tool (`az`) to start and stop the Windows VM. Ensure this
+tool is installed and available in the path on the machine that runs coderd.
diff --git a/examples/templates/azure-windows/main.tf b/examples/templates/azure-windows/main.tf
new file mode 100644
index 0000000000000..6bfe25e78e2c0
--- /dev/null
+++ b/examples/templates/azure-windows/main.tf
@@ -0,0 +1,231 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = "0.7.0"
+ }
+ azurerm = {
+ source = "hashicorp/azurerm"
+ version = "=3.52.0"
+ }
+ }
+}
+
+provider "azurerm" {
+ features {}
+}
+
+provider "coder" {
+}
+
+data "coder_workspace" "me" {}
+
+data "coder_parameter" "location" {
+ description = "What location should your workspace live in?"
+ display_name = "Location"
+ name = "location"
+ default = "eastus"
+ mutable = false
+ option {
+ value = "eastus"
+ name = "East US"
+ }
+ option {
+ value = "centralus"
+ name = "Central US"
+ }
+ option {
+ value = "southcentralus"
+ name = "South Central US"
+ }
+ option {
+ value = "westus2"
+ name = "West US 2"
+ }
+}
+
+data "coder_parameter" "data_disk_size" {
+ description = "Size of your data (F:) drive in GB"
+ display_name = "Data disk size"
+ name = "data_disk_size"
+ default = 20
+ mutable = "false"
+ type = "number"
+ validation {
+ min = 5
+ max = 5000
+ }
+}
+
+resource "coder_agent" "main" {
+ arch = "amd64"
+ auth = "azure-instance-identity"
+ os = "windows"
+ login_before_ready = false
+}
+
+resource "random_password" "admin_password" {
+ length = 16
+ special = true
+ # https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/password-must-meet-complexity-requirements#reference
+ # we remove characters that require special handling in XML, as this is how we pass it to the VM
+ # namely: <>&'"
+ override_special = "~!@#$%^*_-+=`|\\(){}[]:;,.?/"
+}
+
+locals {
+ prefix = "coder-win"
+ admin_username = "coder"
+}
+
+resource "azurerm_resource_group" "main" {
+ name = "${local.prefix}-${data.coder_workspace.me.id}"
+ location = data.coder_parameter.location.value
+ tags = {
+ Coder_Provisioned = "true"
+ }
+}
+
+// Uncomment here and in the azurerm_network_interface resource to obtain a public IP
+#resource "azurerm_public_ip" "main" {
+# name = "publicip"
+# resource_group_name = azurerm_resource_group.main.name
+# location = azurerm_resource_group.main.location
+# allocation_method = "Static"
+# tags = {
+# Coder_Provisioned = "true"
+# }
+#}
+resource "azurerm_virtual_network" "main" {
+ name = "network"
+ address_space = ["10.0.0.0/24"]
+ location = azurerm_resource_group.main.location
+ resource_group_name = azurerm_resource_group.main.name
+ tags = {
+ Coder_Provisioned = "true"
+ }
+}
+resource "azurerm_subnet" "internal" {
+ name = "internal"
+ resource_group_name = azurerm_resource_group.main.name
+ virtual_network_name = azurerm_virtual_network.main.name
+ address_prefixes = ["10.0.0.0/29"]
+}
+resource "azurerm_network_interface" "main" {
+ name = "nic"
+ resource_group_name = azurerm_resource_group.main.name
+ location = azurerm_resource_group.main.location
+ ip_configuration {
+ name = "internal"
+ subnet_id = azurerm_subnet.internal.id
+ private_ip_address_allocation = "Dynamic"
+ // Uncomment for public IP address as well as azurerm_public_ip resource above
+ # public_ip_address_id = azurerm_public_ip.main.id
+ }
+ tags = {
+ Coder_Provisioned = "true"
+ }
+}
+# Create storage account for boot diagnostics
+resource "azurerm_storage_account" "my_storage_account" {
+ name = "diag${random_id.storage_id.hex}"
+ location = azurerm_resource_group.main.location
+ resource_group_name = azurerm_resource_group.main.name
+ account_tier = "Standard"
+ account_replication_type = "LRS"
+}
+# Generate random text for a unique storage account name
+resource "random_id" "storage_id" {
+ keepers = {
+ # Generate a new ID only when a new resource group is defined
+ resource_group = azurerm_resource_group.main.name
+ }
+ byte_length = 8
+}
+
+resource "azurerm_managed_disk" "data" {
+ name = "data_disk"
+ location = azurerm_resource_group.main.location
+ resource_group_name = azurerm_resource_group.main.name
+ storage_account_type = "Standard_LRS"
+ create_option = "Empty"
+ disk_size_gb = data.coder_parameter.data_disk_size.value
+}
+
+# Create virtual machine
+resource "azurerm_windows_virtual_machine" "main" {
+ name = "vm"
+ admin_username = local.admin_username
+ admin_password = random_password.admin_password.result
+ location = azurerm_resource_group.main.location
+ resource_group_name = azurerm_resource_group.main.name
+ network_interface_ids = [azurerm_network_interface.main.id]
+ size = "Standard_DS1_v2"
+ custom_data = base64encode(
+ templatefile("${path.module}/Initialize.ps1.tftpl", { init_script = coder_agent.main.init_script })
+ )
+ os_disk {
+ name = "myOsDisk"
+ caching = "ReadWrite"
+ storage_account_type = "Premium_LRS"
+ }
+ source_image_reference {
+ publisher = "MicrosoftWindowsServer"
+ offer = "WindowsServer"
+ sku = "2022-datacenter-azure-edition"
+ version = "latest"
+ }
+ additional_unattend_content {
+ content = "${random_password.admin_password.result}true1${local.admin_username}"
+ setting = "AutoLogon"
+ }
+ additional_unattend_content {
+ content = file("${path.module}/FirstLogonCommands.xml")
+ setting = "FirstLogonCommands"
+ }
+ boot_diagnostics {
+ storage_account_uri = azurerm_storage_account.my_storage_account.primary_blob_endpoint
+ }
+ tags = {
+ Coder_Provisioned = "true"
+ }
+}
+
+resource "coder_metadata" "rdp_login" {
+ resource_id = azurerm_windows_virtual_machine.main.id
+ item {
+ key = "Username"
+ value = local.admin_username
+ }
+ item {
+ key = "Password"
+ value = random_password.admin_password.result
+ sensitive = true
+ }
+}
+
+resource "azurerm_virtual_machine_data_disk_attachment" "main_data" {
+ managed_disk_id = azurerm_managed_disk.data.id
+ virtual_machine_id = azurerm_windows_virtual_machine.main.id
+ lun = "10"
+ caching = "ReadWrite"
+}
+
+# Stop the VM
+resource "null_resource" "stop_vm" {
+ count = data.coder_workspace.me.transition == "stop" ? 1 : 0
+ depends_on = [azurerm_windows_virtual_machine.main]
+ provisioner "local-exec" {
+ # Use deallocate so the VM is not charged
+ command = "az vm deallocate --ids ${azurerm_windows_virtual_machine.main.id}"
+ }
+}
+
+# Start the VM
+resource "null_resource" "start" {
+ count = data.coder_workspace.me.transition == "start" ? 1 : 0
+ depends_on = [azurerm_windows_virtual_machine.main]
+ provisioner "local-exec" {
+ command = "az vm start --ids ${azurerm_windows_virtual_machine.main.id}"
+ }
+}