Configuring Active Directory for VMs to automatically join a domain

This tutorial shows you how to configure Active Directory and Compute Engine so that Windows virtual machine (VM) instances can automatically join an Active Directory domain.

Automating the process of joining Windows VMs to Active Directory helps you simplify the process of provisioning Windows servers. The approach also allows you to take advantage of autoscaling without sacrificing the benefits of using Active Directory to manage access and configuration.

This tutorial is intended for system admins and assumes that you are familiar with Active Directory and Google Cloud networking.

Objectives

  • Deploy a Cloud Run app that enables VM instances from selected projects to automatically join your Active Directory domain.
  • Create a Cloud Scheduler job that periodically scans your Active Directory domain for stale computer accounts and removes them.
  • Test the setup by creating an autoscaled managed instance group (MIGs) of domain-joined VM instances.

Costs

This tutorial uses the following billable components of Google Cloud:

The tutorial is designed to keep your resource usage within the limits of Google Cloud's Always Free tier. To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

Before you begin

This tutorial assumes that you've already deployed Active Directory on Google Cloud by using Managed Service for Microsoft Active Directory (Managed Microsoft AD) or by deploying self-managed domain controllers on Google Cloud.

To complete this tutorial, ensure that you have the following:

  • Administrative access to your Active Directory domain, including the ability to create users, groups, and organizational units (OUs).
  • An unused /28 CIDR IP range in the VPC that your Active Directory domain controllers are deployed in. You use this IP range to configure Serverless VPC Access.
  • A subnet into which you deploy Windows instances. The subnet must be configured to use Private Google Access.

If you use a self-managed domain controller, you also need the following:

  • A private DNS forwarding zone that forwards DNS queries to your Active Directory domain controllers.

Implementing this approach

In an on-premises environment, you might rely on answer files (unattend.xml) and the JoinDomain customization to automatically join new computers to a domain. Although you can use the same process in Google Cloud, this approach has several limitations:

  • Using a customized unattend.xml file requires that you maintain a custom Compute Engine image. Keeping a custom image current using Windows Updates requires either ongoing maintenance or initial work to set up automation. Unless you need to maintain a custom image for other reasons, this extra effort might not be justified.
  • Using the JoinDomain customization ties an image to a single Active Directory domain because the domain name must be specified in unattend.xml. If you maintain multiple Active Directory domains or forests (for example, for separate testing and production environments), then you might need to maintain multiple custom images for each domain.
  • Joining a Windows computer to a domain requires user credentials that have permissions to create a computer object in the directory. If you use the JoinDomain customization in unattend.xml, you must embed these credentials as plaintext in unattend.xml. These embedded credentials can turn the image into a potential target for attackers. Although you can control access to the image by setting appropriate Identity and Access Management (IAM) permissions, managing access to a custom image adds unnecessary complexity.

The approach that this tutorial takes does not use answer files and therefore does not require specially prepared images. Instead, you use the following sysprep specialize scriptlet when you create a VM instance:

iex((New-Object System.Net.WebClient).DownloadString('https://[DOMAIN]'))

This sysprep specialize scriptlet initiates a process that the following diagram illustrates.

Process that the sysprep specialize scriptlet initiates.

The process works as follows:

  1. After a VM instance is created, Windows boots for the first time. As part of the specialize configuration pass, Windows runs the sysprep specialize scriptlet. The specialize scriptlet invokes the register-computer Cloud Run app and downloads a PowerShell script that controls the domain joining process.
  2. Windows invokes the downloaded PowerShell script.
  3. The PowerShell script calls the metadata server to obtain an ID token that securely identifies the VM instance.
  4. The script calls the register-computer app again, passing the ID token to authenticate itself.
  5. The app validates the ID token and extracts the name, zone, and Google Cloud project ID of the VM instance.
  6. The app verifies that the Active Directory domain is configured to permit VM instances from the given project to join the domain. To complete this verification, the app locates and connects to an Active Directory domain controller to check for an organizational unit (OU) whose name matches the Google Cloud project ID from the ID token. If a matching OU is found, then VM instances of the project are authorized to join the Active Directory domain in the given OU.
  7. The app verifies that the Google Cloud project is configured to allow VM instances to join Active Directory. To complete this verification, the app checks whether it can access the VM instance by using the Compute Engine API.
  8. If all checks pass successfully, the app prestages a computer account in Active Directory. The app saves the VM instance's name, zone, and ID as attributes in the computer account object so that it can be associated with the VM instance.
  9. Using the Kerberos set password protocol, the app then assigns a random password to the computer account.
  10. The computer name and password are returned to the Windows instance over a TLS-secured channel.
  11. Using the prestaged computer account, the PowerShell script joins the computer to the domain.
  12. After the specialize configuration pass is complete, the machine reboots itself.

The remainder of this tutorial walks you through the steps that are required to set up automated domain joining.

Preparing the Active Directory domain

First, you prepare your Active Directory domain. To complete this step, you need a machine that has administrative access to your Active Directory domain.

Optional: Limit who can join computers to the domain

You might want to restrict who can join computers to the domain. By default, the Group Policy Object (GPO) configuration for the Default Domain Controller Policy grants the Add workstations to domain user right to all authenticated users. Anyone with that user right can join computers to the domain. Because you are automating the process of joining computers to your Active Directory domain, universally granting this level of access is an unnecessary security risk.

To limit who can join computers to your Active Directory domain, change the default configuration of the Default Domain Controller Policy GPO:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open the Group Policy Management Console (GPMC).
  3. Go to Forest > Domains > domain-name > Group Policy Objects, where domain-name is the name of your Active Directory domain.
  4. Right-click Default Domain Controller Policy and click Edit.
  5. In the Group Policy Management Editor console, go to Computer Configuration > Policies > Windows Settings > Security Settings > Local Policies > User Rights Assignment.
  6. Double-click Add workstations to domain.
  7. In Properties, remove Authenticated Users from the list.
  8. To let administrators join the domain manually (optional), click Add user or group, and then add an administrative group to the list.
  9. Click OK.

You can now close the Group Policy Management Editor console and GPMC.

Initialize a directory structure

You now create an OU that serves as a container for all project-specific OUs:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open an elevated PowerShell session.
  3. Create a new organizational unit:

    $ParentOrgUnitPath = (Get-ADDomain).ComputersContainer
    $ProjectsOrgUnitPath = New-ADOrganizationalUnit `
      -Name 'Projects' `
      -Path $ParentOrgUnitPath `
      -PassThru
    

Create an Active Directory user account

To access Active Directory and prestage computer accounts, the register-computer app needs an Active Directory user account:

  1. Create an Active Directory user account named register-computer, assign it a random password, and then place it in the Projects OU:

    # Generate a random password
    $Password = [Guid]::NewGuid().ToString()+"-"+[Guid]::NewGuid().ToString()
    
    # Create user
    $UpnSuffix = (Get-ADDomain).DNSRoot
    $RegisterComputerUser = New-ADUser `
        -Name "register-computer Cloud Run app" `
        -GivenName "Register" `
        -Surname "Computer" `
        -Path $ProjectsOrgUnitPath `
        -SamAccountName "register-computer" `
        -UserPrincipalName "register-computer@$UpnSuffix" `
        -AccountPassword (ConvertTo-SecureString "$Password" -AsPlainText -Force) `
        -PasswordNeverExpires $True `
        -Enabled $True `
        -PassThru
    
  2. Grant the register-computer account the minimum set of permissions needed to manage computer accounts and groups in the Projects OU and sub-OUs:

    $AcesForContainerAndDescendents = @(
        "CCDC;Computer",               # Create/delete computers
        "CCDC;Group"                   # Create/delete users
    )
    
    $AcesForDescendents = @(
        "LC;;Computer" ,               # List child objects
        "RC;;Computer" ,               # Read security information
        "WD;;Computer" ,               # Change security information
        "WP;;Computer" ,               # Write properties
        "RP;;Computer" ,               # Read properties
        "CA;Reset Password;Computer",  # ...
        "CA;Change Password;Computer", # ...
        "WS;Validated write to service principal name;Computer",
        "WS;Validated write to DNS host name;Computer",
    
        "LC;;Group",                   # List child objects
        "RC;;Group",                   # Read security information
        "WD;;Group",                   # Change security information
        "WP;;Group",                   # Write properties
        "RP;;Group"                    # Read properties
    )
    
    $AcesForContainerAndDescendents | % { dsacls.exe $ProjectsOrgUnitPath /G "${RegisterComputerUser}:${_}" /I:T | Out-Null }
    $AcesForDescendents | % { dsacls.exe $ProjectsOrgUnitPath /G "${RegisterComputerUser}:${_}" /I:S | Out-Null }
    

    The command might take a few minutes to complete.

  3. Reveal the Projects OU path and the generated password of the register-computer Active Directory user account. Note the values because you will need them later.

    Write-Host "Password: $Password"
    Write-Host "Projects OU: $ProjectsOrgUnitPath"
    

Preparing the Google Cloud project

You now configure your domain project:

  • If you use Managed Microsoft AD, your domain project is the project in which you deployed Managed Microsoft AD.
  • If you use self-managed Active Directory, your domain project is the project that runs your Active Directory domain controllers. In the case of a Shared VPC, this project must be the same as the VPC host project.

You use this domain project to do the following:

  • Create a Secret Manager secret that contains the password of the register-computer Active Directory user account.
  • Deploy Serverless VPC Access to let Cloud Run access Active Directory.
  • Deploy the register-computer app.
  • Configure Cloud Scheduler so that it triggers cleanups of stale computer accounts.

We recommend that you grant access to the domain project on a least-privilege basis.

Create a Secret Manager secret

  1. In the Google Cloud Console, open Cloud Shell.

    Open Cloud Shell

  2. Launch PowerShell:

    pwsh
    
  3. Initialize the following variable, replacing domain-project-id with the ID of your domain project:

    $DomainProjectId = "domain-project-id"
    
  4. Set the domain project as the default project:

    & gcloud config set project $DomainProjectId
    
  5. Enable the Secret Manager API:

    & gcloud services enable secretmanager.googleapis.com
    
  6. Enter the password of the register-computer Active Directory user account and store it in a Secret Manager secret:

    $RegisterComputerCredential = (Get-Credential -Credential 'register-computer')
    
    $TempFile = New-TemporaryFile
    Set-Content $TempFile $($RegisterComputerCredential.GetNetworkCredential().Password) -NoNewLine
    
    & gcloud secrets create ad-password --data-file $TempFile
    
    Remove-Item $TempFile
    

Deploy Serverless VPC Access

The register-computer app needs to communicate with your Active Directory domain controllers. To let Cloud Run access resources in a VPC, you now configure Serverless VPC Access:

  1. Initialize the following variables:

    $VpcName = "vpc-name"
    $ServerlessRegion = "serverless-region"
    $ServerlessIpRange = "serverless-ip-range"
    

    Replace the following:

    • vpc-name: The name of the VPC network that contains your Active Directory domain controllers.
    • serverless-region: The region to deploy your Cloud Run in. Choose a region that supports both Cloud Run and Serverless VPC Access. The region does not have to be the same region as the one you plan to deploy VM instances in.

    • serverless-ip-range: Used by Serverless VPC Access to enable communication between Cloud Run and resources in your VPC. Set to an unreserved /28 CIDR IP range. It must not overlap with any existing subnets in your VPC network.

  2. Enable the Serverless VPC Access APIs:

    & gcloud services enable vpcaccess.googleapis.com
    
  3. Deploy Serverless VPC Access:

    & gcloud compute networks vpc-access connectors create serverless-connector `
        --network $VpcName `
        --region $ServerlessRegion `
        --range $ServerlessIpRange
    

Grant access to Kerberos and LDAP

Although Serverless VPC Access enables Cloud Run to access resources in your VPC, connectivity to the Kerberos and LDAP endpoints of your domain controllers is still subject to firewall rules.

You need to create a firewall rule that permits serverless resources to access your domain controllers by using the following protocols: LDAP (TCP/389), Kerberos (UDP/88, TCP/88), or Kerberos password change (UDP/464, TCP/464). You can apply the rule based on a network tag that you have assigned to your domain controllers, or you can apply it by using a service account.

  • To apply the firewall rule, run one of the following commands in Cloud Shell:

    By network tag

    & gcloud compute firewall-rules create allow-adkrb-from-serverless-to-dc `
        --direction INGRESS `
        --action allow `
        --rules udp:88,tcp:88,tcp:389,udp:464,tcp:464 `
        --source-ranges $ServerlessIpRange `
        --target-tags dc-tag `
        --network $VpcName `
        --project vpc-project-id `
        --priority 10000
    

    Replace the following:

    • dc-tag: The network tag assigned to your domain controller VMs.
    • vpc-project-id: The ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

    By service account

    & gcloud compute firewall-rules create allow-adkrb-from-serverless-to-dc `
        --direction INGRESS `
        --action allow `
        --rules udp:88,tcp:88,tcp:389,udp:464,tcp:464 `
        --source-ranges $ServerlessIpRange `
        --target-service-accounts dc-sa `
        --network $VpcName `
        --project vpc-project-id `
        --priority 10000
    

    Replace the following:

    • dc-sa: The email address of the service account that your domain controller VMs use.
    • vpc-project-id: The ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

Deploy the Cloud Run app

You now set up Cloud Build to deploy the register-computer app to Cloud Run:

  1. In Cloud Shell, clone the GitHub repository:

    & git clone https://github.com/GoogleCloudPlatform/gce-automated-ad-join.git
    cd gce-automated-ad-join/ad-joining
    
  2. Initialize the following variables:

    $AdDomain = "dns-domain-name"
    $AdNetbiosDomain = "netbios-domain-name"
    $ProjectsOrgUnitPath = "projects-ou-distinguished-name"
    

    Replace the following:

    • dns-domain-name: The DNS domain name of your Active Directory domain.
    • netbios-domain-name: The NetBIOS name of your Active Directory domain.
    • projects-ou-distinguished-name: The distinguished name of your Projects OU.
  3. Enable the Cloud Run and Cloud Build APIs:

    & gcloud services enable run.googleapis.com cloudbuild.googleapis.com
    
  4. Create a service account register-computer-app for the Cloud Run app:

    & gcloud iam service-accounts create register-computer-app `
      --display-name="register computer Cloud Run app"
    
  5. Allow the Cloud Run service account to read the secret that contains the Active Directory password:

    & gcloud secrets add-iam-policy-binding ad-password `
      --member "serviceAccount:register-computer-app@$DomainProjectId.iam.gserviceaccount.com" `
      --role "roles/secretmanager.secretAccessor"
    
  6. Grant Cloud Build the necessary permissions to deploy to Cloud Run:

    $DomainProjectNumber = (gcloud projects describe $DomainProjectId --format='value(projectNumber)')
    & gcloud iam service-accounts add-iam-policy-binding register-computer-app@$DomainProjectId.iam.gserviceaccount.com `
      --member "serviceAccount:$DomainProjectNumber@cloudbuild.gserviceaccount.com" `
      --role "roles/iam.serviceAccountUser"
    
    & gcloud projects add-iam-policy-binding $DomainProjectId `
      --member "serviceAccount:$DomainProjectNumber@cloudbuild.gserviceaccount.com" `
      --role roles/run.admin
    
  7. Use the file cloudbuild.yaml as a template to create a custom Cloud Run build config that matches your environment:

    $Build = (Get-Content cloudbuild.yaml)
    $Build = $Build.Replace('__SERVERLESS_REGION__', "$ServerlessRegion")
    $Build = $Build.Replace('__PROJECTS_DN__', "$ProjectsOrgUnitPath")
    $Build = $Build.Replace('__AD_DOMAIN__', "$AdDomain")
    $Build = $Build.Replace('__AD_NETBIOS_DOMAIN__', "$AdNetbiosDomain")
    $Build = $Build.Replace('__SERVICE_ACCOUNT_EMAIL__', "register-computer-app@$DomainProjectId.iam.gserviceaccount.com")
    $Build | Set-Content .\cloudbuild.hydrated.yaml
    
  8. Build the app and deploy it to Cloud Run:

    & gcloud builds submit . `
      --config cloudbuild.hydrated.yaml `
      --substitutions _IMAGE_TAG=$(git rev-parse --short HEAD)
    

    The deployment can take a couple of minutes to complete.

  9. Determine the URL of the Cloud Run app:

    $RegisterUrl = (gcloud run services describe register-computer `
      --platform managed `
      --region $ServerlessRegion `
      --format=value`(status.url`))
    Write-Host $RegisterUrl
    

    Note the URL. You will need it whenever you create a VM instance that should be joined to Active Directory.

  10. Invoke the Cloud Run app to verify that the deployment worked:

    Invoke-RestMethod $RegisterUrl
    

    A PowerShell script displays. The VM runs this script during the specialize phase that joins it to the domain.

Enabling a project for automatic domain joining

The register-computer app does not allow VM instances to join an Active Directory domain unless the VM's project is enabled for automatic domain joining. This security measure helps prevent VMs that are connected to unauthorized projects from accessing your domain.

To enable a project for automatic domain joining, you must do the following:

  • Create an OU in Active Directory whose name matches your Google Cloud project ID.
  • Grant the register-computer app access to the Google Cloud project.

First, create the OU:

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. In the Active Directory Users and Computers MMC snap-in, go to the Projects OU.
  3. Right-click the OU and select New > Organizational Unit.
  4. In the New Object dialog, enter the ID for the Google Cloud project to deploy your VMs in.
  5. Click OK.

Next, grant the register-computer app access to the Google Cloud project:

  1. In Cloud Shell, launch PowerShell:

    pwsh
    
  2. Initialize the following variables:

    $ProjectId = "project-id"
    $DomainProjectId = "domain-project-id"
    

    Replace

    • project-id with the ID of the Google Cloud project to deploy your VMs in
    • domain-project-id with the ID of your domain project
  3. Grant the register-computer-app service account the Compute Viewer role on the project:

    & gcloud projects add-iam-policy-binding $ProjectId `
        --member "serviceAccount:register-computer-app@$DomainProjectId.iam.gserviceaccount.com" `
        --role "roles/compute.viewer"
    

Your project is now ready to support automatic domain joining.

Testing domain joining

You can now verify that the setup is working correctly by:

  • Creating a single VM instance that automatically joins the Active Directory domain
  • Creating a managed instance group of VM instances that automatically join the Active Directory domain

Create and join a single VM instance

Create a VM instance that automatically joins the Active Directory domain:

  1. Return to the PowerShell session in Cloud Shell and initialize the following variables:

    $Region = "vpc-region-to-deploy-vm"
    $Zone = "zone-to-deploy-vm"
    $Subnet = "vpc-subnet-to-deploy-vm"
    $ServerlessRegion = "serverless-region"
    

    Replace the following:

    • vpc-region-to-deploy-vm: The region to deploy the VM instance in.
    • vpc-subnet-to-deploy-vm: The subnet to deploy the VM instance in.
    • zone-to-deploy-vm: The zone to deploy the VM instance in.
    • serverless-region: The region you deployed the Cloud Run app in.
  2. Set the default project and zone:

    & gcloud config set project $ProjectId
    & gcloud config set compute/zone $Zone
    
  3. Lookup the URL of the Cloud Run app again:

    $RegisterUrl = (gcloud run services describe register-computer `
      --platform managed `
      --region $ServerlessRegion `
      --format value`(status.url`) `
      --project $DomainProjectId)
    
  4. Create an instance by passing the specialize scriptlet that causes the VM to join the domain:

    Shared VPC

    $VpchostProjectId = (gcloud compute shared-vpc get-host-project $ProjectId --format=value`(name`))
    & gcloud compute instances create join-01 `
        --image-family windows-2019-core `
        --image-project windows-cloud `
        --machine-type n1-standard-2 `
        --no-address `
        --subnet projects/$VpchostProjectId/regions/$Region/subnetworks/$Subnet `
        --metadata "sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$RegisterUrl'))"
    

    Standalone VPC

    & gcloud compute instances create join-01 `
        --image-family=windows-2019-core `
        --image-project=windows-cloud `
        --machine-type=n1-standard-2 `
        --no-address `
        --subnet $Subnet `
        --metadata "sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$RegisterUrl'))"
    

    If you want to use a custom hostname, add a --hostname parameter to the command.

    If you use a Windows Server version prior to Windows Server 2019, TLS 1.2 might be disabled by default, which can cause the specialize scriptlet to fail. To enable TLS 1.2, use the following scriptlet instead:

    [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;iex((New-Object System.Net.WebClient).DownloadString('$RegisterUrl'))
    
  5. Monitor the boot process:

    & gcloud compute instances tail-serial-port-output join-01
    

    After about one minute, the machine is joined to your Active Directory domain. The output is similar to the following:

    Domain           : corp.example.com
    DomainController : dc-01.corp.example.com.
    OrgUnitPath      : OU=test-project-123,OU=Projects,DC=corp,DC=example,DC=com
    
    WARNING: The changes will take effect after you restart the computer
    
    Computer successfully joined to domain
    

    To stop observing the boot process, press CTRL+C.

Verify that the VM is joined to Active Directory

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. Open the Active Directory Users and Computers MMC snap-in.
  3. In the menu, ensure that View > Advanced Features is enabled.
  4. Go to the OU named after the Cloud project ID that you created a VM instance in.
  5. Double-click the join-01 account.
  6. In the Properties dialog, click the Attribute Editor tab.

    The computer account is annotated with additional LDAP attributes. These attributes let you track the association between the computer object and the Compute Engine instance.

    Verify that the list contains the following LDAP attributes and values.

    LDAP attribute Value
    msDS-cloudExtensionAttribute1 Google Cloud project ID
    msDS-cloudExtensionAttribute2 Compute Engine zone
    msDS-cloudExtensionAttribute3 Compute Engine instance name

    The msDS-cloudExtensionAttribute attributes are general-purpose attributes and are not used by Active Directory itself.

Diagnose errors

If your VM instance failed to join the domain, check the log of the register-computer app:

  1. In the Cloud Console, go to Cloud Run.

    Go to Cloud Run

  2. Click the register-computer app.

  3. In the menu, click Logs.

Delete the instance

After you verify that the VM instance is joined to the Active Directory domain, you delete the instance.

  • Delete the instance:

    & gcloud compute instances delete join-01 --quiet
    

Create and join a managed instance group

You can also verify that instances from a MIG can automatically join your domain.

  1. Create an instance template by passing the specialize script that causes the VM to join the domain:

    Shared VPC

    $VpchostProjectId = (gcloud compute shared-vpc get-host-project $ProjectId --format=value`(name`))
    & gcloud compute instance-templates create ad-2019core-n1-std-2 `
        --image-family windows-2019-core `
        --image-project windows-cloud `
        --no-address `
        --machine-type n1-standard-2 `
        --subnet projects/$VpchostProjectId/regions/$Region/subnetworks/$Subnet `
        --metadata "sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$RegisterUrl'))"
    

    Standalone VPC

    & gcloud compute instance-templates create ad-2019core-n1-std-2 `
        --image-family windows-2019-core `
        --image-project windows-cloud `
        --no-address `
        --machine-type n1-standard-2 `
        --subnet projects/$ProjectId/regions/$Region/subnetworks/$Subnet `
        --metadata "sysprep-specialize-script-ps1=iex((New-Object System.Net.WebClient).DownloadString('$RegisterUrl'))"
    
  2. Create a managed instance group that uses the instance template:

    & gcloud compute instance-groups managed create group-01 `
        --template ad-2019core-n1-std-2 `
        --size=3
    

Wait a few minutes, and then use the Active Directory Users and Computers MMC snap-in to verify that four new objects have been created in Active Directory:

  • 3 computer accounts corresponding to the 3 VM instances of the managed instance group.
  • 1 group named group-01 that contains the 3 computer accounts. If you plan to use group managed service accounts (gMSA), you can use this group to grant access to the gMSA.

After you verify that the VM instances from your MIGs can join your Active Directory domain, you can delete the managed group and instance template by following these steps:

  1. In Cloud Shell, delete the instance group:

    & gcloud compute instance-groups managed delete group-01 --quiet
    
  2. Delete the instance template:

    & gcloud compute instance-templates delete ad-2019core-n1-std-2 --quiet
    

Scheduling cleanup of stale computer accounts

Automating the process of joining computers to the domain reduces the effort to set up new servers and enables you to use domain-joined servers in managed instance groups. Over time, however, stale computer accounts can accumulate in the domain.

To prevent this accumulation, we recommend that you set up the register-computer app to periodically scan your Active Directory domain to find and automatically remove stale accounts.

The register-computer app can use the msDS-cloudExtensionAttribute attributes of computer accounts to identify which computer accounts are stale. These attributes contain the project, zone, and instance name of the corresponding VM instance in Compute Engine. For each computer account, the app can check if the corresponding VM instance is still available. If it is not, then the computer account is considered stale and removed.

To trigger a computer account cleanup, you invoke the /cleanup endpoint of the Cloud Run app. To prevent unauthorized users from triggering a cleanup, this request must be authenticated by using the register-computer-app service account.

Configure Cloud Scheduler

The following steps show you how to set up Cloud Scheduler in conjunction with Pub/Sub to automatically trigger a cleanup once every 24 hours:

  1. In Cloud Shell, enable the Cloud Scheduler API in your domain project:

    & gcloud services enable cloudscheduler.googleapis.com
    
  2. Set AppEngineLocation to a valid App Engine location in which to deploy Cloud Scheduler:

    $AppEngineLocation = "location"
    

    Replace location with the App Engine region that you selected for your VPC resources, for example, us-central. If that region is not available as an App Engine location, choose a location that is geographically close to you. For more information, see Regions and zones.

  3. Initialize App Engine:

    & gcloud app create --region $AppEngineLocation --project $DomainProjectId
    
  4. Create a Cloud Scheduler job:

    & gcloud scheduler jobs create http cleanup-computer-accounts `
        --schedule "every 24 hours" `
        --uri "$RegisterUrl/cleanup" `
        --oidc-service-account-email register-computer-app@$DomainProjectId.iam.gserviceaccount.com `
        --oidc-token-audience "$RegisterUrl/" `
        --project $DomainProjectId
    

    This job calls the register-computer app once every 24 hours and uses the register-computer-app service account for authentication.

Trigger a cleanup

To verify your configuration for cleaning up stale computer accounts, you can trigger the Cloud Scheduler job manually.

  1. In the Cloud Console, go to Cloud Scheduler.

    Go to Cloud Scheduler

  2. For the cleanup-computer-accounts job that you created, click Run Now.

    After a few seconds, the Result column displays Success, indicating that the cleanup completed successfully. If the result column does not update automatically within a few seconds, click the Refresh button.

For more details about which accounts were cleaned up, check the logs of the register-computer app.

  1. In the Cloud Console, go to Cloud Run.

    Go to Cloud Run

  2. Click the register-computer app.

  3. In the menu, click Logs.

    Log entries indicate that the computer accounts of the VM instances you used to test domain joining were identified as stale and removed.

Cleaning up

If you do not want to keep the Google Cloud setup used for this tutorial, you can revert this setup by doing the following:

  1. In Cloud Shell, delete the Cloud Scheduler job:

    & gcloud scheduler jobs delete cleanup-computer-accounts `
      --project $DomainProjectId
    
  2. Delete the Cloud Run app:

    & gcloud run services delete register-computer `
      --platform managed `
      --project $DomainProjectId `
      --region $ServerlessRegion
    
  3. Delete the Secret Manager secret:

    gcloud secrets delete ad-password --project $DomainProjectId
    
  4. Delete the firewall rule for LDAP and Kerberos access:

    gcloud compute firewall-rules delete allow-adkrb-from-serverless-to-dc --project=vpc-project-id
    

    Replace vpc-project-id with the ID of the project the VPC is defined in. If you use a Shared VPC, use the VPC host project; otherwise, use the ID of the domain project.

  5. Delete Serverless VPC Access:

    gcloud compute networks vpc-access connectors delete serverless-connector --region $ServerlessRegion --quiet
    

Revert Active Directory changes

  1. Using an RDP client, log in to a machine that has administrative access to your Active Directory domain.
  2. In the Active Directory Users and Computers MMC snap-in, go to the Projects OU.
  3. Delete the register-computer Active Directory user account.
  4. Delete the OU that you created for testing automated domain joining.

What's next