The other day I published a comprehensive blog post about deploying gMSA on AKS with Terraform. As part of that blog post, I deployed an Azure VM and ran a script on it to deploy Active Directory Domain Services. Today, I wanted to explore that portion in a bit more depth.
Terraform Azure provider
The Terraform Azure provider is our starting point to understand what can be done when deploying Azure VMs with Terraform. The provider is constantly updated with fixes and new features, and we’ll be using the following resources:
- azurerm_resource_group to create a Resource Group.
- azurerm_virtual_network to deploy and an Azure vNet.
- azurerm_subnet to deploy a subnet to the previously created vNet.
- azurerm_network_interface to create a NIC for the VM. This is necessary with Terraform to ensure the VM gets an IP address from the right subnet. With AzCLI or Azure Portal, this is more transparent.
- azurerm_windows_virtual_machine to create a Windows VM. This resource is a replacement from the old azurerm_virtual_machine resource.
The above items are Terraform resources, which means they are Azure resources that will be created via Terraform.
In addition to resources, we need Data to be passed on as part of the creation of the resources. For this example, the only one needed is the “template_file” data type. This is a data source that usually corresponds to a file. The nice thing about this data source is that you can pass on variables that the script will use from the variables that you want to input from Terraform. For example, if the script needs a username and/or password to execute, you might want to set them up as variables that the user will input when the Terraform template is executed.
Finally, there’s another resource we will use to actually execute the script on the Azure side: azurerm_virtual_machine_extension. This resource calls an Azure VM extension, which is the method in Azure to execute a command inside the VM using the Azure API, rather than connecting to the VM.
Terraform template
Here is the terraform template I’ll use to create an Azure VM and then execute the PowerShell script inside of it:
terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "=3.57.0" } }}provider "azurerm" { features { }}#Creates the Azure Resource Groupresource "azurerm_resource_group" "rg" { name = var.resource_group location = var.location}#Creates the Azure Virtual Networkresource "azurerm_virtual_network" "vnet" { name = "testvm" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name address_space = ["192.168.10.0/24"]}#Creates the subnetresource "azurerm_subnet" "testvmsubnet" { name = "testvmsubnet" resource_group_name = azurerm_resource_group.rg.name virtual_network_name = azurerm_virtual_network.vnet.name address_prefixes = ["192.168.10.0/24"]}#Creates a vNIC for the VMresource "azurerm_network_interface" "dc01_nic" { name = "dc01_nic" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "dc01_nic" subnet_id = azurerm_subnet.testvmsubnet.id private_ip_address_allocation = "Dynamic" }}#Creates the Azure VMresource "azurerm_windows_virtual_machine" "dc01" { name = "DC01" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location size = "Standard_D4s_v3" admin_username = var.win_username admin_password = var.win_userpass network_interface_ids = [ azurerm_network_interface.dc01_nic.id ] os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-Datacenter" version = "latest" }}#Install Active Directory on the DC01 VMresource "azurerm_virtual_machine_extension" "install_ad" { name = "install_ad"# resource_group_name = azurerm_resource_group.main.name virtual_machine_id = azurerm_windows_virtual_machine.dc01.id publisher = "Microsoft.Compute" type = "CustomScriptExtension" type_handler_version = "1.9" protected_settings = <<SETTINGS { "commandToExecute": "powershell -command \"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64encode(data.template_file.ADDS.rendered)}')) | Out-File -filepath ADDS.ps1\" && powershell -ExecutionPolicy Unrestricted -File ADDS.ps1 -Domain_DNSName ${data.template_file.ADDS.vars.Domain_DNSName} -Domain_NETBIOSName ${data.template_file.ADDS.vars.Domain_NETBIOSName} -SafeModeAdministratorPassword ${data.template_file.ADDS.vars.SafeModeAdministratorPassword}" } SETTINGS}#Variable input for the ADDS.ps1 scriptdata "template_file" "ADDS" { template = "${file("ADDS.ps1")}" vars = { Domain_DNSName = "${var.Domain_DNSName}" Domain_NETBIOSName = "${var.netbios_name}" SafeModeAdministratorPassword = "${var.SafeModeAdministratorPassword}" }}
You can save the above as main.tf. You’ll also need a variables.tf file:
variable "resource_group" { type = string description = "Resource group name" default = "TestAzVM"}variable "location" { type = string description = "RG and resources location" default = "West US 2"}variable "win_username" { description = "Windows node username" type = string sensitive = false}variable "win_userpass" { description = "Windows node password" type = string sensitive = true}variable "Domain_DNSName" { description = "FQDN for the Active Directory forest root domain" type = string sensitive = false}variable "netbios_name" { description = "NETBIOS name for the AD domain" type = string sensitive = false}variable "SafeModeAdministratorPassword" { description = "Password for AD Safe Mode recovery" type = string sensitive = true}
These are the Terraform files, but you’ll also need the PowerShell script you want to execute.
Using Azure VM extension with Terraform
The main thing when I was creating this deployment was: Should I use the Terraform template to run the script via Azure VM extension or should I use Terraform to just execute the command that runs the script. It seems like the same, but there’s a main difference. The latter, implies that you have a script that is ready to go – as is. That would be ok, if I were simply running a script that does something like changing a registry key or changing the file/folder structure. However, my script to deploy ADDS requires that the user inform a DNS domain name, a NETBIOS domain name and a Safe Mode Admin Password. These are requirements to deploy ADDS via PowerShell. In this case, I don’t want to hard code this information – especially the password – in a PowerShell script. Because of that, I chose to run the script via Terraform calling an Azure VM extension.
First, you’ll need the PowerShell script file that you want to use. Of course, this is the file you want or have already. For my environment and to explain the concept in this blog post, here’s the file I’ll use:
[CmdletBinding()]param ( [Parameter(ValuefromPipeline=$true,Mandatory=$true)] [string]$Domain_DNSName, [Parameter(ValuefromPipeline=$true,Mandatory=$true)] [string]$Domain_NETBIOSName, [Parameter(ValuefromPipeline=$true,Mandatory=$true)] [String]$SafeModeAdministratorPassword)$SMAP = ConvertTo-SecureString -AsPlainText $SafeModeAdministratorPassword -ForceInstall-windowsfeature -name AD-Domain-Services -IncludeManagementToolsInstall-ADDSForest -CreateDnsDelegation:$false -DatabasePath "C:\Windows\NTDS" -DomainMode "WinThreshold" -DomainName $Domain_DNSName -DomainNetbiosName $Domain_NETBIOSName -ForestMode "WinThreshold" -InstallDns:$true -LogPath "C:\Windows\NTDS" -NoRebootOnCompletion:$false -SysvolPath "C:\Windows\SYSVOL" -Force:$true -SkipPreChecks -SafeModeAdministratorPassword $SMAP
I saved this as ADDS.ps1 on the same folder as the main.tf and variables.tf files.
Notice that the PowerShell script has three parameters declared – the same ones I mentioned that are required to deploy ADDS on the VM. Both the DNS Domain Name and NETBIOS name are regular strings, but the Safe Mode Admin Password needs to be converted to secure string, hence the store on the other $SMAP variable. The important thing here is that we’re setting up the script to have the parameters passed on at execution time. That means when someone runs the command to execute the parameter, it will also pass the variables to be used. The final portion of the script is just the execution of the ADDS commands itself – but notice the variables being used.
Now let’s look again at how the Azure VM extension block is composed on the Terraform main.tf file:
#Install Active Directory on the DC01 VMresource "azurerm_virtual_machine_extension" "install_ad" { name = "install_ad"# resource_group_name = azurerm_resource_group.main.name virtual_machine_id = azurerm_windows_virtual_machine.dc01.id publisher = "Microsoft.Compute" type = "CustomScriptExtension" type_handler_version = "1.9" protected_settings = <<SETTINGS { "commandToExecute": "powershell -command \"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64encode(data.template_file.ADDS.rendered)}')) | Out-File -filepath ADDS.ps1\" && powershell -ExecutionPolicy Unrestricted -File ADDS.ps1 -Domain_DNSName ${data.template_file.ADDS.vars.Domain_DNSName} -Domain_NETBIOSName ${data.template_file.ADDS.vars.Domain_NETBIOSName} -SafeModeAdministratorPassword ${data.template_file.ADDS.vars.SafeModeAdministratorPassword}" } SETTINGS}
One of the nicest thing about Terraform is that it figures out the order of executing items on the template. One of the ways it does that, is by calling out which resource precedes this one. In our case, you’ll notice that in order to execute the VM extension, it needs a Virtual Machine ID. This means the VM extension will only run when the VM is properly deployed.
We also pass on some attributes about the VM extension, such as type and handler version. You might need to change this depending on the type of script you’re using.
The most important thing here, though is the protected_settings block. This is where you are saying to the Azure VM extension what command to execute. It starts by saying this needs to be run in a PowerShell session and then the following command.
There’s a bunch of translation that needs to happen between your PowerShell script, Terraform, and the PowerShell session that is being executed on the Azure VM extension. This is why the command starts with the rendering of Base 64 string. This necessary and I have to point out the source on StackOverflow on which I found this.
The next section of the command is an important one. Here we are basically setting up the session to Unrestricted Execution Policy and calling out the ADDS.ps1 script. Note that – as mentioned above – the parameters for the script are being provided here. Not just that, we’re actually passing these variables from the Terraform variables. This makes the whole process not only easier, but way more secure.
However, in order to declare the above variables to be used inside the script, we need to set up as data to be used by Terraform. The next block is what does that:
#Variable input for the ADDS.ps1 scriptdata "template_file" "ADDS" { template = "${file("ADDS.ps1")}" vars = { Domain_DNSName = "${var.Domain_DNSName}" Domain_NETBIOSName = "${var.netbios_name}" SafeModeAdministratorPassword = "${var.SafeModeAdministratorPassword}" }}
The above is simply a map of the variables the script will need from the variables that were input by the user when the Terraform plan is applied.
Running the script
To run the above scenario, save the three files (main.tf, variables.tf, and ADDS.ps1) on the same folder, open a PowerShell session on that folder and run the following:
az loginaz account set <subscription ID>terraform initterraform apply
When you apply the Terraform config, it will ask you for the parameters needed not only for the Azure resources, but the PowerShell script as well:
It will then require that you approve the Terraform plan. It should show the creation of 6 new resources.
Once the deployment is complete, you can open the Azure Portal, connect to the VM and see that ADDS has been installed correctly. Still on the Azure Portal, you can also check the status of the VM extension by opening the VM and clicking Extensions + applications tab, selecting the script we deployed, and checking the information:
Conclusion
It is possible to run PowerShell scripts on Azure VMs via Azure VM extensions with Terraform. You can set up variables to be used by the user at deployment time that are then passed on the script, which not only allows for greater flexibility, but also enhances security as you don’t have to hard code usernames and passwords on your Terraform template or PowerShell script.
I hope this is helpful for you. As always, the above sample is available on our GitHub repo, so you can leverage it, improve it, and collaborate with us if you have any suggestions. And if you have questions, please let us know on the comments below!
FAQs
How do I run a PowerShell script in all Azure VMs? ›
Run Command can run scripts on your virtual machines remotely by using the VM agent. You use Run Command through the Azure portal, REST API, or PowerShell for Windows VMs. This capability is useful in all scenarios where you want to run a script within a virtual machine.
How do I run a PowerShell script automatically in Azure? ›- Sign in to the Azure portal, and navigate to your Automation account.
- Under Process Automation, select Runbooks.
- Select Create a runbook. ...
- Click Create to create the runbook.
- In the runbook editor, paste the following code:
Run Command in Azure Portal
In the Azure portal, navigate to the virtual machine resource. Navigate to Operations > Run Command. Select RunPowerShellScript from the list of commands. Type the PowerShell script content you want to run on the server in the Run Command Script pane.
- On the Hyper-V host, open PowerShell as Administrator.
- Run one of the following commands to create a session using the virtual machine name or GUID: PowerShell Copy. Invoke-Command -VMName <VMName> -ScriptBlock { command } Invoke-Command -VMId <VMId> -ScriptBlock { command }
To run a script on one or many remote computers, use the FilePath parameter of the Invoke-Command cmdlet. The script must be on or accessible to your local computer. The results are returned to your local computer.
How do I allow all scripts in PowerShell? ›- Open PowerShell as administrator.
- In the PowerShell window, type the following command and press Enter: Set-ExecutionPolicy RemoteSigned.
- If prompted, press A to confirm the action.
- Requirements. The requirements for completing the steps in this article depend on your Azure subscription: ...
- Create a resource group. ...
- Provision a new instance. ...
- Create a new application. ...
- Create a new app deployment. ...
- Get a service and its properties. ...
- Get an application. ...
- Get an app deployment.
Run the Scheduled Task on demand:
To run a PowerShell script from the task scheduler, Open task scheduler >> Browse to “Task scheduler Library” >> Right-click on the created task and choose Run.
Go to a VM in the Azure portal and select Run command in the left menu, under Operations. You see a list of the available commands to run on the VM. Choose a command to run. Some of the commands might have optional or required input parameters.
How do I call a PowerShell script in Azure pipeline? ›- Push your script into your repo.
- Add a PowerShell build task.
- Drag the build task where you want it to run.
- Specify the name of the script.
How do I know if Azure VM is running PowerShell? ›
To check if the VMs are running, deallocated, or stopped using PowerShell, we need to use the - Status parameter. If you write only the Get-AzVM command to get the VM details, it won't show up the Azure VM power status default.
Can I run a PowerShell script from the command line? ›You can also run PowerShell scripts from the cmd.exe command interpreter or from a batch file.
How to restart Azure virtual machine using PowerShell script? ›To restart the Azure VM using PowerShell, we can use the az vm restart command. This command requires the Azure VM in the running status. You also require to connect to the Azure cloud account and the proper azure subscription before running this command.
Can Azure CLI run PowerShell script? ›Azure CLI and Azure PowerShell are command-line tools that enable you to create and manage Azure resources. Both are cross-platform, installable on Windows, macOS, and Linux. Runs in Windows PowerShell, Cmd, or Bash and other Unix shells.
How do I run a PowerShell script on multiple computers simultaneously? ›To use a PowerShell script on multiple computers simultaneously, please do the following: Select a computer in your account that enabled the PowerShell app. Click the PowerShell app there and switch to the Script Library (above right button). In the Library, select the script you want to use on the computer.
How do I run a shell script on multiple servers? ›To run commands on multiple servers, add the servers to a hosts file as explained before. Then run pdsh as shown; the flag -w is used to specify the hosts file, and -R is used to specify the remote command module (available remote command modules include ssh, rsh, exec, the default is rsh).
How do I run a PowerShell script remotely as administrator? ›Click on the Start menu (or the Windows key). Type powershell ise in the search box, and select Windows PowerShell ISE. Press CTRL+SHIFT+ENTER to start the ISE (enter the administrator credentials if prompted).
How do I run all PowerShell scripts in a folder? ›To run a script in the current directory, type the path to the current directory, or use a dot to represent the current directory, followed by a path backslash ( . \ ). If the script has parameters, type the parameters and parameter values after the script filename.
How do I make a PowerShell script executable? ›- Open Start.
- Search for PowerShell, right-click the top result, and select the Run as administrator option.
- Type the following command to allow scripts to run and press Enter: Set-ExecutionPolicy RemoteSigned.
In File Explorer (or Windows Explorer), right-click the script file name and then select "Run with PowerShell". The "Run with PowerShell" feature starts a PowerShell session that has an execution policy of Bypass, runs the script, and closes the session.
How do I run a PowerShell script in Azure Data Studio? ›
- Open Azure Data Studio as an administrator and click on the extensions menu.
- In "extensions", search for PowerShell.
- Click on the PowerShell extension result.
- Click on install to install the extension.
To deploy a local template, use the -TemplateFile parameter in the deployment command.
How often do Endpoint Manager scripts run? ›On this final screen review the settings and click Add when done. Scripts are deployed to selected groups once every hour by Intune. You may need to wait at least one hour before the deployment is pushed out to the endpoints.
Can we run PowerShell script from power automate? ›To declare variables in scripting actions and return results in Power Automate, use the following commands: To declare new variables in PowerShell scripts, use the $ notation. To return values from Run PowerShell script actions to Power Automate, use the Write-Output command.
How do I run a PowerShell script continuously in the background? ›- Example. Start-Job -ScriptBlock{Get-EventLog -LogName System} ...
- Output. Id Name PSJobTypeName State HasMoreData Location -- ---- ------------- ----- ----------- -------- 1 Job1 BackgroundJob Running True localhost. ...
- Example.
- Output. ...
- Output. ...
- Example. ...
- Output. ...
- Example.
If the goal is to start a PowerShell script without a console window, you need to launch powershell.exe from a process that does not itself have a console window. A WSH script launched using wscript.exe does not have a console window, so you can write a WSH script that runs powershell.exe in a hidden window.
Can we automate shell script? ›The commands in a shell script are the same as you would use on the command line. Once you have worked out the details and tested your commands in the shell, you can save them into a file so, the next time, you can automate the process with a script.
How do I run a shell script by clicking? ›- Select the file using mouse.
- Right-click on the file.
- Choose Properties:
- Click Permissions tab.
- Select Allow executing file as a program:
- Now click the file name and you will be prompted. Select “Run in the terminal” and it will get executed in the terminal.
To use full path you type sh /home/user/scripts/someScript . sh /path/to/file is different from /path/to/file . sh runs /bin/sh which is symlinked to /bin/dash . Just making something clear on the examples you see on the net, normally you see sh ./somescript which can also be typed as `sh /path/to/script/scriptitself'.
How do I run a terraform script in Azure? ›- Configure your environment. Azure subscription: If you don't have an Azure subscription, create a free account before you begin.
- Open Cloud Shell. ...
- Install latest version of Terraform in Azure Cloud Shell. ...
- Verify the default Azure subscription. ...
- Authenticate Terraform to Azure.
How do I run a shell script step by step? ›
- Open the terminal. Go to the directory where you want to create your script.
- Create a file with . sh extension.
- Write the script in the file using an editor.
- Make the script executable with command chmod +x <fileName>.
- Run the script using ./<fileName>.
- Use kubectl exec to open a bash command shell where you can execute commands. kubectl exec -it pod-name -- /bin/bash. The following example gets a shell to the suitecrm-0 pod: ...
- Use kubectl exec to execute commands directly. kubectl exec -it pod-name -- /bin/bash -c " command(s) "
The two most common PowerShell methods of interacting with REST API's are to use either Invoke-RestMethod or Invoke-WebRequest. To interact with a REST API the best option is to use Invoke-RestMethod. Invoke-RestMethod turns input JSON or XML into native PowerShell objects to make further interaction easy.
Which command should I use to connect to Azure PowerShell? ›To sign in interactively, use the Connect-AzAccount cmdlet. This cmdlet presents an interactive browser based login prompt by default. Use the Get-AzContext cmdlet to store your tenant ID in a variable to be used in the next two sections of this article.
What is the PowerShell CLI command to start a VMS in Azure? ›The Start-AzVM cmdlet starts an Azure virtual machine.
Where do I run Azure PowerShell script? ›If you need to run PowerShell scripts for virtual machines without having to RDP into a virtual machine or if you do not have access to the virtual machines at the OS level, you can do so through the Azure portal's Run Command under the VM plane.
How do I start and stop Azure VM using PowerShell? ›- From your workstation, use the PowerShell Connect-AzAccount cmdlet to sign in to your Azure account. ...
- Provide values for the <lab name> and <VM name> , and enter which action you want for <Start or Stop> . ...
- Start or stop the VM, based on the value you passed to $vmAction .
Another way to determine what application your script is running under is to use $Host.Name. If the script is running under PowerShell Studio or PrimalScript, it will return 'PrimalScriptHostImplementation'. If the script is running under the ISE, it will return 'Windows PowerShell ISE Host'.
How do I get all VM PowerShell? ›To see all VMs on the local Hyper-V host, you should run the Get-VM PowerShell cmdlet. On the PowerShell screen, you can see the list of available VMs, including their name, state, CPU usage, memory assigned, uptime, status, and version.
What is easy way to download and run scripts on your Azure VMs? ›Download and run scripts in Azure virtual machines. Can be run using Azure Resource Manager templates, Azure CLI, REST API, PowerShell, or Azure portal. Script files can be downloaded from Azure storage or GitHub, or provided from your PC when run from the Azure portal.
How do I run a SQL script on multiple servers using PowerShell? ›
Execute SQL Script on Multiple Servers
Using the simple example (db tales com) at the top, create a new “myquery. sql” file, paste the following and save the file.. This is what will be executed on each instance. Modify the Powershell to execute this script file instead of the “-Query” and add the results to the array.
Windows environment variables are visible as a PS drive called Env: To list all the environment variables use: Get-Childitem env: (or just dir env:)
How to get all servers from AD PowerShell? ›- Identify the domain from which you want to retrieve the report.
- Identify the LDAP attributes you need to fetch the report.
- Compile the script.
- Execute it in Windows PowerShell.
- The report will be exported in the given format.
To view all environment variables in the current PowerShell session, you can run the command: Get-ChildItem Env: This is equivalent to running the Set command in Cmd.exe. returns The ALLUSERSPROFILE variable is C:\ProgramData.
How do I automate a PowerShell script? ›- Step 1: Open Task Scheduler from the Run window.
- Step 2: Right-click on the Task Scheduler (Local) in the left panel and click Create Task. ...
- Step 3: Add parameters for the task in the Create Task window and click OK to save changes.
- Step 4: Switch to the Trigger tab.
The Start-AzVM cmdlet starts an Azure virtual machine.
How do I run multiple PowerShell scripts from a batch file? ›The commands can be in one PowerShell invocation by using a SEMICOLON at the end of each line. You need just one execution of PowerShell, and multiple commands passed to it. Each of those commands should be separated with semicolons, ; , and should all be enclosed within one doublequoted -Command argument.
How do I run a query in PowerShell? ›- $SQLServer = "TestServerOne"
- $db3 = "TestDB3"
- $selectdata = "SELECT Id, IdData FROM invokeTable"
- Invoke-Sqlcmd -ServerInstance $SQLServer -Database $db3 -InputFile "C:\Files\TSQL\run.sql"
- Invoke-Sqlcmd -ServerInstance $SQLServer -Database $db3 -Query $selectdata.