Skip to content

Deploy GCP Windows Desktop (HyOps Blueprint)

  • Purpose: Provision a Windows Server VM in GCP with a public IP and an RDP firewall rule scoped to your operator IP.
  • Owner: Platform engineering
  • Trigger: Need for a GCP Windows host: GUI access, Windows-only tooling, or operator lab
  • Impact: Creates a GCP Compute instance and a named firewall rule; incurs GCP compute cost while running
  • Severity: P2
  • Pre-reqs: hyops init gcp complete for the target env, gcloud SDK installed, Windows Server quota available in target zone.
  • Rollback strategy: hyops blueprint destroy tears down both steps in reverse order. See §Teardown.

Context

Blueprint ref: gcp/windows-desktop@v1 Location: hybridops-core/blueprints/gcp/windows-desktop@v1/blueprint.yml

Step flow:

  1. platform/gcp/platform-vm#gcp_windows_vm: Windows Server 2022 VM, public IP, allow-rdp tag
  2. platform/gcp/vm-firewall-rules#gcp_windows_firewall: ingress rule TCP 3389, scoped to source_ranges

Both steps must succeed. The firewall rule is only useful once the VM is running, so step 2 requires step 1 (requires: [gcp_windows_vm]).

The VM does not use SSH keys (ssh_keys_from_init: false). RDP credentials are generated by GCP and retrieved via gcloud compute reset-windows-password after provisioning.


Cost

An e2-standard-2 Windows Server 2022 VM in europe-west2 costs roughly £0.09–0.11/hr depending on region. Stop the instance when not in use to pause compute billing. The firewall rule has no cost.

Stop:

gcloud compute instances stop win-rdp-01 \
  --project <PROJECT_ID> --zone <ZONE>

Start:

gcloud compute instances start win-rdp-01 \
  --project <PROJECT_ID> --zone <ZONE>

Preconditions and safety checks

  1. GCP init is ready:

    hyops state show --env dev --init gcp
    
  2. Confirm your current public IP (used to scope the RDP firewall rule):

    curl -s https://checkip.amazonaws.com
    
  3. Confirm Windows Server quota in the target zone:

    gcloud compute regions describe <REGION> \
      --project <PROJECT_ID> \
      --format="table(quotas[].metric,quotas[].usage,quotas[].limit)" \
      | grep CPUS
    
  4. Review the two CHANGE_ME values you must set before deploy:

  5. CHANGE_ME_GCP_ZONE: e.g., europe-west2-b
  6. CHANGE_ME_YOUR_IP/32: your operator public IP in CIDR notation

Steps

  1. Initialise the blueprint config
    hyops blueprint init --env dev \
      --ref gcp/windows-desktop@v1 \
      --dest-name windows-desktop.yml
    

This writes the blueprint to:

    ~/.hybridops/envs/dev/config/blueprints/windows-desktop.yml
  1. Set real values

Open ~/.hybridops/envs/dev/config/blueprints/windows-desktop.yml and replace:

  • CHANGE_ME_GCP_ZONE → e.g., europe-west2-b
  • CHANGE_ME_YOUR_IP/32 → your public IP, e.g., 203.0.113.42/32

Optional overrides (defaults are usable as-is):

  • machine_type: default e2-standard-2; increase if the workload needs more CPU/RAM
  • boot_disk_size_gb: default 80
  • source_image_family: default windows-2022; change to windows-2019 if needed

  • Validate

    hyops blueprint validate \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/windows-desktop.yml"
    
  • Preflight

    hyops blueprint preflight --env dev \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/windows-desktop.yml"
    
  • Deploy

    hyops blueprint deploy --env dev \
      --file "$HOME/.hybridops/envs/dev/config/blueprints/windows-desktop.yml" \
      --execute
    

Both steps run in sequence. Typical duration: 3–6 minutes (Windows boot is slower than Linux).

  1. Verify state
    cat ~/.hybridops/envs/dev/state/modules/platform__gcp__platform-vm/instances/gcp_windows_vm.json \
      | python3 -m json.tool | grep -E '"status"|"state_instance"'
    
    cat ~/.hybridops/envs/dev/state/modules/platform__gcp__vm-firewall-rules/instances/gcp_windows_firewall.json \
      | python3 -m json.tool | grep '"status"'
    

Both should show "status": "ok".

  1. Get the public IP
    cat ~/.hybridops/envs/dev/state/modules/platform__gcp__platform-vm/instances/gcp_windows_vm.json \
      | python3 -m json.tool
    

Look for outputs.ipv4_addresses.win-rdp-01. Or directly:

    gcloud compute instances describe win-rdp-01 \
      --project <PROJECT_ID> --zone <ZONE> \
      --format="get(networkInterfaces[0].accessConfigs[0].natIP)"
  1. Get RDP credentials

Windows VMs do not use SSH keys. Retrieve a GCP-generated username and password:

    gcloud compute reset-windows-password win-rdp-01 \
      --project <PROJECT_ID> \
      --zone <ZONE>

This outputs a username and password. Store them securely: GCP does not retain the plaintext password after this call.

  1. RDP in

Connect with any RDP client to <PUBLIC_IP>:3389 using the credentials from step 8.

  • Windows: built-in Remote Desktop Connection (mstsc)
  • macOS: Microsoft Remote Desktop (App Store)
  • Linux: rdesktop or xfreerdp

Example with xfreerdp:

    xfreerdp /v:<PUBLIC_IP> /u:<USERNAME> /p:<PASSWORD> /cert-ignore

Verification

Primary state:

~/.hybridops/envs/<env>/state/modules/platform__gcp__platform-vm/instances/gcp_windows_vm.json
~/.hybridops/envs/<env>/state/modules/platform__gcp__vm-firewall-rules/instances/gcp_windows_firewall.json

Cross-check in GCP:

# VM running
gcloud compute instances describe win-rdp-01 \
  --project <PROJECT_ID> --zone <ZONE> \
  --format="get(status)"

# Firewall rule present
gcloud compute firewall-rules list \
  --project <PROJECT_ID> \
  --filter="targetTags~allow-rdp" \
  --format="table(name,sourceRanges,allowed)"

Teardown

Destroy all blueprint resources in reverse order using the blueprint file:

hyops blueprint destroy --env dev \
  --file "$HOME/.hybridops/envs/dev/config/blueprints/windows-desktop.yml" \
  --execute

This destroys steps in the order: firewall → VM. A confirmation prompt lists both steps and their current state before proceeding. Use --yes to skip the prompt in non-interactive contexts.

Manual teardown (individual steps)

If only specific steps need retargeting, destroy each module individually. The --inputs flag is required because zone and other required inputs are not stored in the state record directly.

# Destroy the firewall rules
hyops destroy --env dev \
  --module platform/gcp/vm-firewall-rules \
  --state-instance gcp_windows_firewall \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__vm-firewall-rules/instances/gcp_windows_firewall.inputs.yml"

# Destroy the VM
hyops destroy --env dev \
  --module platform/gcp/platform-vm \
  --state-instance gcp_windows_vm \
  --inputs "$HOME/.hybridops/envs/dev/config/modules/platform__gcp__platform-vm/instances/gcp_windows_vm.inputs.yml"

Troubleshooting

RDP connection refused

Check (in order):

  1. Public IP is correct: retrieve it again via gcloud compute instances describe
  2. Firewall rule allow-rdp exists and source_ranges includes your current IP (your IP may have changed)
  3. VM status is RUNNING:

    gcloud compute instances describe win-rdp-01 \
      --project <PROJECT_ID> --zone <ZONE> \
      --format="get(status)"
    
  4. Windows RDP service is ready: Windows takes 3–5 minutes after the VM reports RUNNING before RDP is accepting connections

gcloud compute reset-windows-password fails with permission error

Cause: operator lacks compute.instances.setMetadata permission.

Fix: ensure operator has roles/compute.instanceAdmin.v1 or a custom role with that permission on the project.

VM state stuck at STAGING

Cause: zone capacity or quota issue.

Fix:

  • Try a different zone in the same region (e.g., europe-west2-a instead of europe-west2-b)
  • Check quota: gcloud compute regions describe <REGION> --format="table(quotas[].metric,quotas[].usage,quotas[].limit)" | grep CPU
  • Retry hyops apply with an updated zone in the blueprint config

Error: resource already exists

Cause: a firewall rule with the same computed name already exists outside state.

Fix: rename context_id in the blueprint config (e.g., desktop2) to generate a different rule name, or delete the orphaned GCP rule manually before applying.


References