- A Go AWS SDK example - Fri, Nov 11 2022
- Getting started with Jenkins - Tue, Aug 16 2022
- Pulumi vs. Terraform - Tue, Jul 5 2022
If you have not heard of or used Vagrant before, then it's well worth checking out some of the excellent blogs here on 4sysops by Adam Bertram. What is so good about Vagrant is that there are many ways to provision a machine, Ansible, Chef, Puppet, and shell scripting to name just a few.
The ability to provision with shell scripts allows us to use all the PowerShell language, including PowerShell DSC. PowerShell DSC is a declarative platform we can use to configure and manage systems.
There really are only two parts to this: the DSC configuration and the Vagrantfile to set up and provision the machine. In this example, I've created a DSC configuration to install PowerShell Core and OpenSSH on a Windows Server 2016. I will also be making use of the Vagrant boxes available from the Vagrant site.
PowerShell DSC configuration
Before getting to the Vagrant configuration, I shall build my DSC configuration. This straightforward configuration will hopefully show the potential of this method to provision a machine.
The first part of the configuration is very similar to a PowerShell function:
Configuration PS6TestServer { param ( [ValidateNotNullOrEmpty()] [string]$Node, [ValidateNotNullOrEmpty()] [string]$MOFfolder ) Import-DscResource -ModuleName PSDesiredStateConfiguration
I'm declaring a copy of parameters: one for the name of the machine and one for the destination of the Managed Object Format (MOF) file. These values will come from the Vagrant configuration file—more on that in a bit.
Node $Node { File DSCLogFolder { Type = 'Directory' DestinationPath = 'C:\Tmp\DSClogs' Ensure = "Present" } Package PSCore { Ensure = "Present" Name = "PowerShell 6-x64" Path = "$Env:SystemDrive\tmp\SourceFiles\PowerShell-6.1.0- preview.1-win-x64.msi" ProductId = "A16A59B1-5DB1-44FC-BD40-E684B43B3A8E" LogPath = "C:\Tmp\DSClogs\PS6.log" } Log AfterPSCoreInstall { Message = "Finished Installing PowerShell Core 6 resource with ID PSCore" DependsOn = "[Package]PSCore" }
The first part is to create a log file directory for the install of PowerShell Core; the next is the install of PowerShell Core. I've downloaded a copy of the MSI install, which I will copy later to the machine through the Vagrantfile.
Script InstallOpenSSH { GetScript = { $folderSize = Get-ChildItem -Path 'C:\Program Files\OpenSSH\OpenSSH-Win64' | Measure-Object -property Length -sum | Select-Object Sum # return true or false return @{ Result = ($folderSize.Sum -eq '7390211') } } SetScript = { param( [string] $From = "C:\Tmp\SourceFiles\OpenSSH-Win64.zip", [string] $To = "C:\Program Files\OpenSSH\", [string] $Installer = "C:\Program Files\OpenSSH\OpenSSH-Win64\install-sshd.ps1" ) if (Test-Path $From) { # Load assembly name to perform unzip Add-Type -AssemblyName System.IO.Compression.FileSystem # Unzip file to directory [System.IO.Compression.ZipFile]::ExtractToDirectory($From, $To) # Run install & $Installer } } TestScript = { # The Test function needs to return True if the system is already in the desired state $testpath = Test-Path "$Env:SystemDrive\Program Files\OpenSSH\OpenSSH-Win64" if ($testpath) { $count = (Get-ChildItem -Path "$Env:SystemDrive\Program Files\OpenSSH\OpenSSH-Win64").count if ($count -eq 18) { return $true } } else { return $false } } } Log AfterInstallOpenSSH { Message = "Finished Installing Open SSH resource with ID InstallOpenSSH" DependsOn = "[Script]InstallOpenSSH" } } }
The second part is using the PowerShell DSC script resource to install OpenSSH. We need to define three scriptblocks: GetScript, SetScript, and TestScript.
- The GetScript scriptblock should return a hash table representing the state of the current node.
- The TestScript scriptblock should determine if the current node requires modification.
- The SetScript scriptblock should modify the node. The DSC calls it if the TestScript block return $false.
To read more about the script resource, please refer to the Microsoft documentation.
The main scriptblock SetScript unzips the file and runs the ps1 installer for OpenSSH.
The Vagrantfile
This is where everything comes together. The file will set the Vagrant box we will use, configurations to perform, and call and invoke our DSC code.
Let's look at the Vagrantfile first, then explain what is happening:
# -*- mode: ruby -*- # vi: set ft=ruby : # Set variables Node = "DSCPSCore6" MOFfolder = "C:\\tmp\\MOF" CopySource = "D:\\VagrantDemo\\DSC\\SourceFiles" CopyDestination = "C:\\tmp\\SourceFiles" Vagrant.configure("2") do |config| config.vm.box = "gusztavvargadr/w16s" config.vm.communicator = "winrm" # Set vagrant machine name config.vm.hostname = Node # Configure network config.vm.network "forwarded_port", host: 33389, guest: 3389 config.vm.network "forwarded_port", host: 8080, guest: 80 config.vm.network "forwarded_port", host: 4443, guest: 443 # Perform file copy from Local machine to Vagrant box config.vm.provision "file", source: CopySource, destination: CopyDestination # Create MOF config.vm.provision "shell", path: 'D:\VagrantDemo\DSC\Config\PS6TestServer.ps1', args: [Node, MOFfolder] # Invoke MOF file config.vm.provision "shell", inline: "Start-DSCConfiguration -Path $Args[0] -Force -Wait -Verbose", args: [MOFfolder] end
As you can see, for all that's going on, there isn't much to this Vagrantfile. The Vagrantfile itself is written in Ruby. You don't need to know Ruby inside and out to set up a Vagrant build, so don't let it put you off!
I've commented the above code to help. The first part sets variables for the rest of the code. This simply makes it easier to read and change it for future builds. The file has a few quirks, like having to use double backslashes (\\) for folder separation. The next two lines state which Vagrant box we are going to use (In this case, I've used a Windows 2016 Server from the Vagrant boxes site) and how to communicate with the box.
I'm using the Node variable I set at the start to name the server, followed by setting up forwarding ports for the network. The provisioning steps come next. The first one File copies files or directories from the source machine to our target Vagrant box. I am copying the MSI install for PowerShell Core and the ZIP file to install OpenSSH from my machine. These files need to be in place for our DSC configuration to work.
The second part is running the PowerShell DSC configuration file from the local machine. At the bottom of my DSC file, I added the following:
# For Vagrant output Write-Host "MySite DSC Config :: Node=$($args[0]), MOFfolder=$($args[1])" # Call the configuration to generate MOF file PS6TestServer -Node $args[0] -OutputPath $args[1]
The args part of the Vagrantfile will pass in our two variables: the name of the server (Vagrant box) and the file location for the MOF our DSC configuration created. The DSC file will run locally on our machine and generate the MOF file on the server. I've used PowerShell's built-in variable $args to pass the Node and MOFfolder variables from the Vagrantfile. Write-Host displays the variables passed. The square brackets in Ruby denote an array.
The last step is running Start-DscConfiguration on the Vagrant box to apply our DSC configuration, using the MOFfolder variable to locate the newly generated MOF file.
Performing the build
Now let's put it all together. I used this folder structure:
Don't worry about the .vagrant folder; the Vagrant box you choose and typing vagrant up creates this. I created the DSC folder with Config and SourceFiles subfolders. The Config folder is for my DSC code, and SourceFiles is for my installers.
The next part is to bring up the Vagrant box. From your main folder containing your Vagrantfile, type vagrant up. If all goes well, you will see the setup run through. Here is some of the output from my build:
Bringing machine 'default' up with 'virtualbox' provider... ==> default: Checking if box 'gusztavvargadr/w16s' is up to date... ==> default: Clearing any previously set forwarded ports... ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 3389 (guest) => 33389 (host) (adapter 1) default: 80 (guest) => 8080 (host) (adapter 1) default: 443 (guest) => 4443 (host) (adapter 1) default: 5985 (guest) => 55985 (host) (adapter 1) default: 5986 (guest) => 55986 (host) (adapter 1) default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: WinRM address: 127.0.0.1:55985 default: WinRM username: vagrant default: WinRM execution_time_limit: PT2H default: WinRM transport: negotiate ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... ==> default: Setting hostname... ==> default: Mounting shared folders... default: /vagrant => D:/VagrantDemo ==> default: Running provisioner: file... ==> default: Running provisioner: shell... default: Running: D:\VagrantDemo\DSC\Config\PS6TestServer.ps1 as c:\tmp\vagrant-shell.ps1 default: MySite DSC Config :: Node=DSCPSCore6, MOFfolder=C:\tmp\MOF default: Directory: C:\tmp\MOF default: Mode LastWriteTime Length Name default: ---- ------------- ------ ---- default: -a---- 4/7/2018 2:57 PM 8630 DSCPSCore6.mof ==> default: Running provisioner: shell... default: Running: inline PowerShell script default: VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = default: SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = default: root/Microsoft/Windows/DesiredStateConfiguration'. default: VERBOSE: An LCM method call arrived from computer DSCPSCORE6 with user sid default: S-1-5-21-333254102-1508906537-909750630-1000. default: VERBOSE: [DSCPSCORE6]: LCM: [ Start Set ] default: VERBOSE: [DSCPSCORE6]: LCM: [ Start Resource ] [[File]DSCLogFolder] default: VERBOSE: [DSCPSCORE6]: LCM: [ Start Test ] [[File]DSCLogFolder] …………………………. default: VERBOSE: [DSCPSCORE6]: LCM: [ Start Set ] [[Log]AfterInstallOpenSSH] default: VERBOSE: [DSCPSCORE6]: LCM: [ End Set ] [[Log]AfterInstallOpenSSH] in 0.0000 seconds. default: VERBOSE: [DSCPSCORE6]: LCM: [ End Resource ] [[Log]AfterInstallOpenSSH] default: VERBOSE: [DSCPSCORE6]: LCM: [ End Set ] default: VERBOSE: [DSCPSCORE6]: LCM: [ End Set ] in 0.4790 seconds. default: VERBOSE: Operation 'Invoke CimMethod' complete. default: VERBOSE: Time taken for configuration job to complete is 0.606 seconds
It is certainly satisfying to see all the stages complete successfully! By running the vagrant PowerShell command, I can test whether our DSC configuration created the install folders:
Now you can type vagrant rdp or vagrant powershell to access your newly provisioned box.
Should you wish to try this example out for yourself, I've added the code to my GitHub.
Subscribe to 4sysops newsletter!
As you can see, Vagrant is very useful tool for testing. Coupled with PowerShell DSC, you can create your own custom build, and then create and remove it as many times as you like and still have the same results when provisioning your vagrant box.
Thanks, this helped. I was more specifically looking to copy ps1 files on windows VM and run it there on guest node. My host is Mac.