Nano Server Packages in Windows Server 2016 TP5

Happy TP5 day!

Here is just a quick snapshot of the package list for Nano Server in Windows Server 2016 TP5:

ss_nanotp5_packagelist

Right off the bat I notice a few new ones are included:

  • BootFromWim
  • SecureStartup
  • ShieldedVM

I can guess at the purpose of these packages, but will be interested to learn a bit more about them. That is all I have time for as I now need to go and update the LabBuilder sample projects to use this new ISO.

 

 

Install a VMWare ESXi 6.0 Hypervisor in a Hyper-V VM

Update 19th February 2018:

This article has had a lot more attention than I ever expected! This has bought to light several issues with the process as well as changes made to Hyper-V and ESXi that break the process. You could wade through all 256 comments and assemble the corrections yourself, but user @Gambit has helpfully done this for me! So I’ve included the summary here:

@Tom Watson: Adding a line to /etc/vmware/config with vmx.allowNested = “TRUE”
Since all VM’s will be running nested (whole point of article) this is a must if you want them to start! The alternative is adding this line to every VMX file for each guest, Toms solution was much more elegant.

@burnett437: Configure ESXi Management vSwitch “accept” “promiscuous mode”. Apparently this was a known issue even in nested ESXi 5.5. While the host network will operate for a while without problem, the “promiscuous mode” policy will eventually be tripped and you wont be able to talk to the host at random times (I originally thought it was stability issues with the Tulip driver). Now I think this has to due with the nature of nested virtual switches (a VMWare vSwitch insude of a Hyper-V Virtual Switch). When this happens I found you could “down/up” the management vnicX to get it back, but just set the setting not worry about it.

@me: Network threat detection doesn’t like nested virtual switches either. My Symantec Endpoint Client would occasionally block traffic coming from my local NIC (vSphere Client/vSphere Converter) to this nested Host (Windows Firewall did NOT seem to care), but SEP occasionally tripped cause all “suspicious” addressing to the nested host.

@me: Use vSphere converter for guest images coming from other VMWare products….duh. ESXi doesn’t like split vhdk’s, guest won’t boot. This is a beginner mistake, but this article is for Hyper-V admins that may not know the subtle nuances between VMWare products.

@RichMD: I like the idea of hardware pass-through on the NIC. I may tinker with this because it may resolve several issues, not just ESXi v6.5 NIC blacklist issue.
Theoretically, passing the physical NIC directly to the nested ESXi Host could/should resolve the “promiscuous mode”, “Network Threat Protection” and even the “Half Duplex Legacy Adapter Requirement” (very slow network performance) problems. I may revisit this…I think my server board has a 2nd NIC I could try this with if I get the time.

The original article starts here:

Recently I’ve been playing around with the new Hyper-V Nested Virtualization feature within Windows 10 (build 10565 and greater) and Windows Server 2016. It is pretty cool to be able to create virtualized lab environments running that contain Hyper-V clusters. But what if we want a lab that contains VMWare ESXi Hypervisors running on Hyper-V host. I couldn’t find the process documented anywhere and I couldn’t even confirm if it should be possible. But after lots of asking a lot of annoying questions – thanks Adam Burns – Googling and hair pulling I managed to get it going:

ss_vmwareinhv_proof

So this seems like a good topic for a blog post.

What You’ll Need

You are going to need a few things to get this working:

  • A Hyper-V host running on Windows 10 (built 10565 or greater) or Windows Server 2016 TP4.
  • Enable-NestedVM.ps1 – A PowerShell script for enabling Nested Virtualization in a Hyper-V VM. Click here to get the file from the Microsoft team on GitHub.
  • A VMWare account – just sign up for one here if you don’t already have one.
  • VMWare PowerShell CLI installed – I used 6.3 release 1 that I downloaded from here.
  • ESXi-Customizer-PS.ps1 – A PowerShell script for injecting network drivers into an ESXi 5.x/6.x ISO. I downloaded it from here.

I suggest you download all of the above items to a working folder – I called mine d:\ESX-In-Hyper-V, so these instructions will reflect that, but you can call your folder what ever you like.

You should end up with a folder containing these files:

ss_vmwareinhv_neededfiles

And before you ask: No, you don’t need an VMWare ESXi 6.0 ISO – this will get downloaded and produced for us.

The Process

Part 1 – Prepare an ESXi 6.0 ISO with Network Drivers

The biggest problem I ran into when trying to install ESXi onto Hyper-V was that the ESXi kernel doesn’t come with drivers for the Microsoft Virtual Network Adapter or the Microsoft Legacy Network Adapter (emulates a DECchip 21140). So you’ll need to inject these drivers into the VMWare ESXi 6.0 ISO. Luckily there is a script available and the appropriate drivers DECchip 21140 (called “net-tulip” for some reason) that makes this process a breeze:

  1. Install WMWare PowerCLI.
  2. Open a PowerShell console.
  3. Enter the following commands:
    CD D:\ESX-In-Hyper-V\
    .\ESXi-Customizer-PS-v2.4.ps1 -v60 -vft -load net-tulip
    
  4. After a few minutes the VMWare ESXi 6.0 ISO will be downloaded and the “net-tulip” drivers merged with it:

ss_vmwareinhv_createesxiiso

The ISO will now be available in the D:\ESX-In-Hyper-V folder:

ss_vmwareinhv_neededfilesandiso

Part 2 – Create the Hyper-V VM

  1. In Hyper-V Manager create a new Virtual Machine:ss_vmwareinhv_newvmpath
  2. Click Next.
  3. Select Generation 1 and click Next.
  4. Set the Startup Memory to at least 4096MB.
  5. Uncheck Use Dynamic Memory for this Virtual Machine:ss_vmwareinhv_newvmmemory
  6. Click Next.
  7. Don’t bother to Configure Networking on the next step – just click Next.
  8. Select Create a new virtual hard disk and set the Size to 10GB (this is just going to be the boot disk for the ESXi Hypervisor):ss_vmwareinhv_newvmdisk
  9. Click Next.
  10. Select Install an operating system from a bootable CD/DVD-ROM.
  11. Select Image file (.iso) and browse to the ISO created in Part 1.ss_vmwareinhv_newvminstallation
  12. Click Next then click Finish to create the Virtual Machine:ss_vmwareinhv_newvm
  13. Right click the new Virtual Machine and select Settings.
  14. Select the Processor node and increase the Number of Virtual Processors to at least 2:ss_vmwareinhv_vmsettings_processor
  15. Select the existing Network Adapter node and click Remove:ss_vmwareinhv_vmsettings_removenetwork
  16. Select the Add Hardware node and select Legacy Network Adapter:ss_vmwareinhv_vmsettings_addnetwork
  17. Click Add.ss_vmwareinhv_vmsettings_addlegacy
  18. Select a Virtual Switch to connect the ESXi Host to.
  19. Click OK.

The Virtual Machine is almost ready to start up, but there is one more thing to do.

Part 3 – Enable Nested Virtualization

Before the starting up the Virtual Machine we need to enable Nested Virtualization Extensions on it. This is done by running a PowerShell script.

  1. Open a PowerShell console.
  2. Enter the following commands (adjusting the vmName to match the name of your Virtual Machine):
    CD D:\ESX-In-Hyper-V\
    .\Enable-NestedVm.ps1 -vmName 'VMWARE ESXi Host 1'
    
  3. Enter Y when asked to confirm any of the changes:ss_vmwareinhv_enablenestedvirtualization
  4. The Virtual Machine is now ready to have ESXi installed into it.

If you run into any problems with enabling nested virtualization, I’d recommend reviewing the documentation. Covering all the possible ways Nested Virtualization might not be configured correctly is beyond the scope of this post. Also, this is still a preview feature and so may still have issues.

Part 4 – Boot ESXi Virtual Machine

  1. Start up the ESXi Virtual Machine and make sure you’re connected to it so you can see the ESXi boot screen:ss_vmwareinhv_bootfirst
  2. Quickly press Tab.
  3. Add the ignoreHeadless=TRUE to the Boot Options: ss_vmwareinhv_bootoptions
  4. Press Enter.
  5. The ESXi Installation system will start up.ss_vmwareinhv_bootscreenfirst
  6. After a couple of minutes the VMWare ESXi 6.0.0 Installer will start up:ss_vmwareinhv_esxiinstaller
  7. You can now go through the ESXi installation process.
  8. You will receive this warning during the installation process but you can ignore it:ss_vmwareinhv_esxiinstallerwaring
  9. The installation process will begin:ss_vmwareinhv_esxiinstallerinstall
  10. Once the ESXi installation has completed you will see this message:ss_vmwareinhv_esxiinstallercomplete
  11. Eject the ESXi Installation ISO before rebooting the Virtual Machine:ss_vmwareinhv_ejectiso
  12. Press Enter to reboot the VM.

Part 5 – Configure the ESXi Boot Options

The final thing we have to do is permanently set the boot options for the ESXi host so that the ignoreHeadless setting is always set to TRUE.

  1. When the ESXi machine reboots, quickly press SHIFT-O to set the boot options.
  2. Add the ignoreHeadless=TRUE to the Boot Options:ss_vmwareinhv_bootsecondoptions
  3. Press Enter to boot up the ESXi host:ss_vmwareinhv_bootsecond_started
  4. Once the ESXi has booted up, press F2.
  5. Enter the root login credentials that were set during the ESXi installation process.
  6. Select Troubleshooting Options and press Enter.
  7. Select Enable ESXi Shell and press Enter:ss_vmwareinhv_bootsecond_enableshell
  8. Press ALT+F1 to bring up the console:ss_vmwareinhv_bootsecond_console
  9. Enter your root credentials.
  10. Enter the following command:
    esxcfg-advcfg --set-kernel "TRUE" ignoreHeadless

    ss_vmwareinhv_bootsecond_command

  11. Press ALT+F2 to return to the main ESXi screen.

The ESXi host can now be restarted without having to worry about the ignoreHeadless=TRUE setting.

You now have a fully running ESXi Host running inside a Hyper-V Virtual Machine. I shouldn’t have to point out that this is a completely unsupported way of installing an ESXi Host and should never be used for production workloads. But at least we now have a way of running ESXi Hosts in a Hyper-V Lab environment.

Here’s hoping that someone finds this useful!

Get an Array of Localized Hyper-V Integration Service Names

Today’s PowerShell snippet is used to get a list of Localized captions for the available Integration Services available on a Hyper-V host. I needed this because LabBuilder allows the individual Integration Services to be enabled or disabled per Lab Virtual Machine.

It does this using the Integration Service names configured in the configuration XML file. The problem of course is localization – something I often overlook. If you need to enable/disable an Integration Service on a VM, you need to know the name of it. The name of course is a localized string, so you need to know what the possible values are on the current machine culture.

So, after a lot of digging around in the WMI/CIM I managed to locate the various classes I need and converted them into a simple function:

function GetIntegrationServiceNames {
[CmdLetBinding()]
param
(
)
$Captions = @()
$Classes = @(
'Msvm_VssComponentSettingData'
'Msvm_ShutdownComponentSettingData'
'Msvm_TimeSyncComponentSettingData'
'Msvm_HeartbeatComponentSettingData'
'Msvm_GuestServiceInterfaceComponentSettingData'
'Msvm_KvpExchangeComponentSettingData'
)
foreach ($Class in $Classes)
{
$Captions += (Get-CimInstance `
-Class $Class `
-Namespace Root\Virtualization\V2 `
-Property Caption | Select-Object -First 1).Caption
} # foreach
# This Integration Service is registered in CIM but are not exposed in Hyper-V
# 'Msvm_RdvComponentSettingData'
return $Captions
} # GetIntegrationServiceNames
GetIntegrationServiceNames

The output of the function looks like this for English US:

VSS
Shutdown
Time Synchronization
Heartbeat
Guest Service Interface
Key-Value Pair Exchange

Hopefully someone will find it handy.

Install Jenkins using DSC – Part 2

In my previous post I showed how to create a PowerShell script that would install a Jenkins CI Master server onto a Windows Server Core installation. The obvious next step for such a script was to convert it into a DSC configuration file.

In this post I’m assuming WMF 5.0 is installed onto the server that will be converted into a Jenkins Master. You could manage this without WMF 5.0, but you’d need to manually install the DSC Resource modules that the configuration will use.

Once again, the full DSC Configuration script can be found at the end of the post.

Requirements

You’ll need:

  • A physical or virtual machine running Windows Server 2012 R2 Core (or Full) – it should be a completely clean install with WMF 5.0 installed on it.
  • An administrator login to the server.
  • An internet connection to the server.

Resource Modules

This DSC Configuration requires the use of three DSC Resources:

  • cChoco – this community resource is used to install Chocolatey and Jenkins.
  • xNetworking – this resource is used to configure the networking on the server if required.
  • PSDesiredStateConfiguration – this resource comes with PowerShell by default and is used to provide the Script resource.

The easiest way to install these resource modules is by executing these commands on the Jenkins server:

# Make sure the DSC Resource modules are downloaded
Install-Module -Name cChoco -Force
Install-Module -Name xNetworking -Force

However, if you’re using a Pull server or compiling the DSC MOF on a development machine (rather than the Jenkins node) you would need to use other methods of ensuring the modules are available.

The Configuration Components

The DSC Configuration needs to do the following things:

  • Configure Networking (optional)
  • Install .NET 3.5 Framework
  • Install Chocolatey
  • Install JDK 8
  • Install Jenkins
  • Configure Jenkins Port (optional)

Configure Networking

I like to use the xNetwoking DSC resource to configure the IPv4 and IPv6 settings on the Network adapter to have a static configuration. However, you won’t need to do this if you’re using DHCP or manual configuration. Note, in my case my adapter was called “Ethernet”.

xIPAddress IPv4_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv4'
    IPAddress      = '192.168.128.20'
    SubnetMask     = '24'
}
xDefaultGatewayAddress IPv4G_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv4'
    Address        = '192.168.128.19'
}
xDnsServerAddress IPv4D_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv4'
    Address        = '192.168.128.10'
}
xIPAddress IPv6_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv6'
    IPAddress      = 'fd53:ccc5:895a:bc00::14'
    SubnetMask     = '64'
}
xDefaultGatewayAddress IPv6G_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv6'
    Address        = 'fd53:ccc5:895a:bc00::13'
}
xDnsServerAddress IPv6D_1 {
    InterfaceAlias = 'Ethernet'
    AddressFamily  = 'IPv6'
    Address        = 'fd53:ccc5:895a:bc00::a'
}

Install .NET 3.5 Framework

Jenkins requires the .NET 3.5 Framework, so I’m going to use the WindowsFeature DSC Resource to install it:

WindowsFeature NetFrameworkCore
{
    Ensure    = "Present"
    Name      = "NET-Framework-Core"
}

Install Chocolatey

Next up, I’m going to use the cChocoInstaller resource in the cChoco resource module (available on PowerShell Gallery here) to install the Chocolatey package manager:

# Install Chocolatey
cChocoInstaller installChoco
{
    InstallDir = "c:\choco"
    DependsOn = "[WindowsFeature]NetFrameworkCore"
}

Install JDK 8 and Jenkins

The cChocoPackageInstaller resource module is the used to install JDK 8 and Jenkins

# Install JDK8
cChocoPackageInstaller installJdk8
{
    Name = "jdk8"
    DependsOn = "[cChocoInstaller]installChoco"
}

# Install Jenkins
cChocoPackageInstaller installJenkins
{
    Name = "Jenkins"
    DependsOn = "[cChocoInstaller]installChoco"
}

Configure Jenkins Port

The last step of the configuration is optional. By default Jenkins is configured to listen on port 8080, however I want to change it to 80. So this next part uses the Script resource to change the “–httpPort” setting in the Jenkins.xml file. I use Regex to do this:

# Set the Jenkins Port
Script SetJenkinsPort
{
	SetScript = {
		Write-Verbose -Verbose "Setting Jenkins Port to $Using:JenkinsPort"
		$Config = Get-Content `
			-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
		$NewConfig = $Config `
			-replace '--httpPort=[0-9]*\s',"--httpPort=$Using:JenkinsPort "
		Set-Content `
			-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml" `
			-Value $NewConfig `
			-Force
		Write-Verbose -Verbose "Restarting Jenkins"
		Restart-Service `
			-Name Jenkins
	}
	GetScript = {
		$Config = Get-Content `
			-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
		$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
		$CurrentPort = $Matches.Groups[1].Value
		Return @{
			'JenkinsPort' = $CurrentPort
		}
	}
	TestScript = {
		$Config = Get-Content `
			-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
		$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
		$CurrentPort = $Matches.Groups[1].Value

		If ($Using:JenkinsPort -ne $CurrentPort) {
			# Jenkins port must be changed
			Return $False
		}
		# Jenkins is already on correct port
		Return $True
	}
	DependsOn = "[cChocoPackageInstaller]installJenkins"
}

Create the MOF

The final thing to do is download the cChoco and xNetworking DSC Resources,create the MOF and then ask the LCM to apply it:

$ConfigData = @{
    AllNodes =
    @(
        @{
            NodeName = "LocalHost"
        }
    )
}

JENKINS_CI -JenkinsPort 80 -ConfigurationData $ConfigData

Start-DscConfiguration -Path .\JENKINS_CI -Wait -Verbose

The Complete DSC Configuration

Here is the complete DSC Configuration file. You just need to copy it to the Server and run it. It will compile the configuration into a MOF and tell the LCM to apply it. Just remember to ensure required DSC Resource modules are installed.

Configuration JENKINS_CI
{
param (
$JenkinsPort = 8080
)
Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
Import-DscResource -ModuleName 'cChoco'
Import-DscResource -ModuleName 'xNetworking'
Node $AllNodes.NodeName {
# Configure networking (optional)
xIPAddress IPv4_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv4'
IPAddress = '192.168.128.20'
SubnetMask = '24'
}
xDefaultGatewayAddress IPv4G_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv4'
Address = '192.168.128.19'
}
xDnsServerAddress IPv4D_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv4'
Address = '192.168.128.10'
}
xIPAddress IPv6_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv6'
IPAddress = 'fd53:ccc5:895a:bc00::14'
SubnetMask = '64'
}
xDefaultGatewayAddress IPv6G_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv6'
Address = 'fd53:ccc5:895a:bc00::13'
}
xDnsServerAddress IPv6D_1 {
InterfaceAlias = 'Ethernet'
AddressFamily = 'IPv6'
Address = 'fd53:ccc5:895a:bc00::a'
}
# Install .NET 3.5
WindowsFeature NetFrameworkCore
{
Ensure = "Present"
Name = "NET-Framework-Core"
}
# Install Chocolatey
cChocoInstaller installChoco
{
InstallDir = "c:\choco"
DependsOn = "[WindowsFeature]NetFrameworkCore"
}
# Install JDK8
cChocoPackageInstaller installJdk8
{
Name = "jdk8"
DependsOn = "[cChocoInstaller]installChoco"
}
# Install Jenkins
cChocoPackageInstaller installJenkins
{
Name = "Jenkins"
DependsOn = "[cChocoInstaller]installChoco"
}
# Set the Jenkins Port
Script SetJenkinsPort
{
SetScript = {
Write-Verbose -Verbose "Setting Jenkins Port to $Using:JenkinsPort"
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$NewConfig = $Config `
-replace '--httpPort=[0-9]*\s',"--httpPort=$Using:JenkinsPort "
Set-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml" `
-Value $NewConfig `
-Force
Write-Verbose -Verbose "Restarting Jenkins"
Restart-Service `
-Name Jenkins
}
GetScript = {
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
$CurrentPort = $Matches.Groups[1].Value
Return @{
'JenkinsPort' = $CurrentPort
}
}
TestScript = {
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
$CurrentPort = $Matches.Groups[1].Value
If ($Using:JenkinsPort -ne $CurrentPort) {
# Jenkins port must be changed
Return $False
}
# Jenkins is already on correct port
Return $True
}
DependsOn = "[cChocoPackageInstaller]installJenkins"
}
}
}
$ConfigData = @{
AllNodes =
@(
@{
NodeName = "LocalHost"
}
)
}
JENKINS_CI -JenkinsPort 80 -ConfigurationData $ConfigData
Start-DscConfiguration -Path .\JENKINS_CI -Wait -Verbose

Within five to ten minutes the Jenkins server will be configured and ready to go.

Install Jenkins on Windows Server Core – Part 1

I’ll admit it- I love Windows Server Core and I use it whenever possible. I think everyone should try and do the same. However, I know not everyone is a PowerShell expert or has any desire to be one.

So for this blog post I’m going to show how I created a simple script that will install Jenkins CI on a Windows Server Core system to be a Jenkins Master server. Feel free to just skip down to the end and use the completed script if you want to. I did this on a Windows Server 2012 R2 Core system, but this would probably work on Windows Server 2016 TP4 (the currently available version). You could of course use this on a Windows Server Full system as well.

Note: Installing a Windows Server Core as a Jenkins Slave server is a similar process but there is no need to install the Jenkins Server software or service. I won’t cover the process of installing a Windows Jenkins Slave in this post.

This is post is part one of a two part post. In the part two I’ll convert the process over to a DSC configuration that can be applied to one or more nodes to make the process even easier and ensure your Jenkins servers maintain their state.

Requirements

You’ll need:

  • A physical or virtual machine running Windows Server 2012 R2 Core (or Full) – it should be a completely clean install with nothing already installed.
  • An administrator login to the server.
  • An internet connection to the server.

The Script

The first thing I like to do is get all the variables into one place so I can easily see what options I might want to set. In this case the only thing I care about is setting a static IP Address details of the server and also the port Jenkins will be assigned to:

 

# Configure the settings to use to setup this Jenkins Executor
$Port = 80
$IPAddress = '192.168.1.96'
$SubnetPrefixLength = 24
$DNSServers = @('192.168.1.1')
$DefaultGateway = '192.168.1.1'

The next thing I need to do is ensure the .NET Framework v3.5 is installed (required by Jenkins on Windows):

# Install .NET Framework 3.5
Install-WindowsFeature -Name NET-Framework-Core

For this installation I’m actually going to let the Chocolatey package manager do most of the heavy lifting of actually downloading and installing the Jenkins bits. So I need to install Chocolatey:

# Install Chocolatey
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))

Next up, I use Chocolatey to install both the Oracle JDK 8 and the Jenkins bits. These will be downloaded off the internet so may take a little while depending on your connection. The -y parameter forces the install to occur without prompting:

# Install Chocolatey
# Install JDK 8
choco install jdk8 -y

# Install Jenkins using Chocolatey
choco install Jenkins -y

What I’m going to do next is configure the port Jenkins should run on. This is done by changing the –httpPort setting in the c:\program files (x86)\Jenkins\Jenkins.xml file. I’ll use a simple RegEx to do this. Also, because the Jenkins Service is already running at this point I’ll need to restart it before the changed setting will be read:

# Set the port Jenkins uses
$Config = Get-Content `
  -Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$NewConfig = $Config `
  -replace '--httpPort=[0-9]*\s',"--httpPort=$Port "
Set-Content `
  -Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml" `
  -Value $NewConfig `
  -Force
Restart-Service `
  -Name Jenkins

The Chocolatey Jenkins package automatically configures a firewall rule named “Jenkins” that allows inbound traffic to the Java.exe application. This means that external machines will be able to connect to this Jenkins server. You may want to change this by removing the “Jenkins” firewall rule and replace it with something more specific to your needs, however I didn’t do this in my script.

The final section is optional – it just configures the network connection on the machine to use a static IP address. You could omit this section completely if you were using DHCP or some other method of configuring the network connection:

# Set a static IP Address - optional
New-NetIPAddress `
 -IPAddress $IPAddress `
 -InterfaceAlias Ethernet `
 -DefaultGateway $DefaultGateway `
 -AddressFamily IPv4 `
 -PrefixLength $SubnetPrefixLength
Set-DnsClientServerAddress `
 -InterfaceAlias Ethernet `
 -Addresses $DNSServers

That’s all there is to it.

The Complete Script

Here is the complete script. You can just fire up PowerShell on the Core server and copy/paste this directly into the PowerShell console, or use some other method of running it:

# Configure the settings to use to setup this Jenkins Executor
$Port = 80
$IPAddress = '192.168.1.96'
$SubnetPrefixLength = 24
$DNSServers = @('192.168.1.1')
$DefaultGateway = '192.168.1.1'
# Install .NET Framework 3.5
Install-WindowsFeature -Name NET-Framework-Core
# Install Chocolatey
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
# Install JDK 8
choco install jdk8 -y
# Install Jenkins using Chocolatey
choco install Jenkins -y
# Set the port Jenkins uses
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$NewConfig = $Config `
-replace '--httpPort=[0-9]*\s',"--httpPort=$Port "
Set-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml" `
-Value $NewConfig `
-Force
Restart-Service `
-Name Jenkins
# Set a static IP Address - optional
New-NetIPAddress `
-IPAddress $IPAddress `
-InterfaceAlias Ethernet `
-DefaultGateway $DefaultGateway `
-AddressFamily IPv4 `
-PrefixLength $SubnetPrefixLength
Set-DnsClientServerAddress `
-InterfaceAlias Ethernet `
-Addresses $DNSServers

Tomorrow I’ll improve on this process by converting it into a DSC configuration, which will ensure the Jenkins Server maintains it’s state and makes provisioning them even easier.

Thanks for reading.

 

 

Which Physical Network Adapters are bound to Virtual Switches?

Today’s post has quite a long title for what is going to be a fairly short post. While making some improvements to LabBuilder, I had a need to find out which physical network adapters on a host are bound to Hyper-V Virtual Switches. This is because a single physical adapter can only be bound to a single External Virtual Switch.

So I wrote a few lines of PowerShell that would do the trick:

$MacAddress = `
(Get-VMNetworkAdapter `
-ManagementOS `
-Name (Get-VMSwitch | ? {
$_.SwitchType -eq 'External'
}).Name).MacAddress
Get-NetAdapter -Physical | ? {
($_.MacAddress -replace '-','') -in $MacAddress
}

The first piece gets a list of MAC addresses for all Virtual Network Adapters that are configured for use by the host OS (managementOS) on External Switches.

The second piece then gets the list of Physical network adapters that match the MAC addresses from the first line. I had to use a –Replace to get rid of the dashes in the Physical network adapter MAC address so that I could compare it with the MAC Address in the Virtual Network Adapters. It would be nice if the MAC address format was standard across all modules, but it is a pretty minor complaint.

So as you can see, PowerShell makes this unbelievably easy. This piece of code allows me to ensure that when LabBuilder is creating a new External Switch it doesn’t use a physical adapter that has already been used.

Configuring iSCSI and iSNS with DSC

Several months back I created a DSC Resource for configuring iSCSI Server Targets (including Virtual Disks) as well as iSCSI Initiators using Desired State Configuration (DSC). I created this for several reasons:

  1. I needed a way for LabBuilder to automatically build Scale-Out File Servers (with CSVs).
  2. I needed something to use as an example in my Creating Professional DSC Resources series.
  3. No one else had already created one.

This weekend I decided to add iSNS Server support to the resource – for both the ciSCSIServerTarget and ciSCSIInitiator resources. So with that feature added I thought it might be a good opportunity for me to write a quick blog post on how to use these DSC Resources.

Installing the Resource

You can find the new ciSCSI resource in the PowerShell Gallery.

For those of you using Windows Management Framework 5.0 (or have the PowerShellGet module installed) you can just use the command:

Install-Module -Name ciSCSI

If you don’t have Windows Management Framework 5.0 (and don’t have the PowerShellGet module installed) you will need to download and install the resource from the GitHub Repository.

Using the Resource

If you’d rather just jump right into the resource documentation and examples you can find it here. Otherwise, read on and I’ll cover this resource to configure both an iSCSI Server Target and an iSCSI Initiator. I’ll also show how to register iSCSI Server Targets and Initiators with an iSNS Server.

Important: Although the ciSCSI DSC Resource will work on Windows Management Framework 4.0, these examples require the use of the WaitForAny DSC Resource, which is only available in Windows Management Framework 5.0. This resource is used to ensure that the iSCSI Server Target has been created before trying to connect any iSCSI Initiators to it. The resource could be omitted, but errors will reported by the LCM on the iSCSI Initiator computers if the iSCSI Server Target is not available before the iSCSI Initiator DSC MOF is applied.

The Example Environment

In this example, the DSC Configurations that are being created will refer to the following servers:

  • FS1.CONTOSO.COM – this is the file server that will contain the iSCSI Virtual Disks and iSCSI Server Target.
  • CLUS1.CONTOSO.COM,CLUS2.CONTOSO.COM,CLUS3.CONTOSO.COM – these are the Windows Server 2012 R2 (or Windows Server 2016) Cluster Server nodes that will be connecting to the iSCSI Server Target.
  • ISNS1.CONTOSO.COM – this is a server with the iSNS Server Windows Feature installed on it. The iSNS default domain has been configured on this server already.

The DSC configurations that will be created will create four 128GB dynamic iSCSI Virtual Disks on the D:\ drive of FS1.CONTOSO.COM. An iSCSI Server Target called FS1-Server-Target will be created and the four iSCSI Virtual Disks attached to it.

Configuring the iSCSI Server Target

A DSC configuration that creates an iSCSI Server Target requires the following steps to be performed in the DSC Resource:

  1. Install the iSCSI Target Server Windows Feature (FS-iSCSITarget-Server).
  2. Initialize and physical disks that will be used to store the iSCSI Virtual Disks (optional).
  3. Create the iSCSI Virtual Disks that will be used by the iSCSI Server Target.
  4. Create the iSCSI Server Target and optionally register it with an iSNS Server.

Here is the DSC Configuration:

Configuration ISCSI_SERVER_TARGET
{
Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
Import-DscResource -ModuleName xStorage
Import-DscResource -ModuleName ciSCSI
Node 'FS1' {
# Step 1 - Install the iSCSI Target Server Windows Feature (FS-iSCSITarget-Server).
WindowsFeature ISCSITargetServerInstall
{
Ensure = "Present"
Name = "FS-iSCSITarget-Server"
}
# Step 2 - Initialize and physical disks that will be used to store the iSCSI Virtual Disks (optional).
xWaitforDisk Disk2
{
DiskNumber = 1
RetryIntervalSec = 60
RetryCount = 60
}
xDisk DVolume
{
DiskNumber = 1
DriveLetter = 'D'
DependsOn = "[xWaitforDisk]Disk2"
}
File VirtualDisksFolder
{
Ensure = 'Present'
DestinationPath = 'D:\iSCSIVirtualDisks'
Type = 'Directory'
DependsOn = '[xDisk]DVolume'
}
# Step 3 - Create the iSCSI Virtual Disks that will be used by the iSCSI Server Target
ciSCSIVirtualDisk VDisk1
{
Ensure = 'Present'
Path = 'D:\iSCSIVirtualDisks\VDisk1.vhdx'
DiskType = 'Dynamic'
SizeBytes = 128GB
DependsOn = "[File]VirtualDisksFolder"
}
ciSCSIVirtualDisk VDisk2
{
Ensure = 'Present'
Path = 'D:\iSCSIVirtualDisks\VDisk2.vhdx'
DiskType = 'Dynamic'
SizeBytes = 128GB
DependsOn = "[File]VirtualDisksFolder"
}
ciSCSIVirtualDisk VDisk3
{
Ensure = 'Present'
Path = 'D:\iSCSIVirtualDisks\VDisk3.vhdx'
DiskType = 'Dynamic'
SizeBytes = 128GB
DependsOn = "[File]VirtualDisksFolder"
}
ciSCSIVirtualDisk VDisk4
{
Ensure = 'Present'
Path = 'D:\iSCSIVirtualDisks\VDisk4.vhdx'
DiskType = 'Dynamic'
SizeBytes = 128GB
DependsOn = "[File]VirtualDisksFolder"
}
# Step 4 - Create the iSCSI Server Target and optionally register it with an iSNS Server.
ciSCSIServerTarget iSCSIServerTarget
{
Ensure = 'Present'
TargetName = 'FS1-Server-Target'
InitiatorIds = @(
'Iqn:iqn.1991-05.com.microsoft:CLUS1.CONTOSO.COM'
'Iqn:iqn.1991-05.com.microsoft:CLUS2.CONTOSO.COM'
'Iqn:iqn.1991-05.com.microsoft:CLUS3.CONTOSO.COM'
)
Paths = @(
'D:\iSCSIVirtualDisks\VDisk1.vhdx'
'D:\iSCSIVirtualDisks\VDisk2.vhdx'
'D:\iSCSIVirtualDisks\VDisk3.vhdx'
'D:\iSCSIVirtualDisks\VDisk4.vhdx'
)
iSNSServer = 'isns1.contoso.com'
}
}
}

Important: Note that the TargetName is set to ‘FS1-Server-Target‘, which will automatically configure the Target IQN to ‘iqn.1991-05.com.microsoft:FS1-FS1-Server-Target-Target’. This is because the Microsoft iSCSI Server Target cmdlets automatically name the Server Target for you using the following format:

"iqn.1991-05.com.microsoft:$($ComputerName)-$($ServerTarget)-Target"

This is very important to remember because the iSCSI Initiators use this string to identify the Server Target to connect to.

The rest of the components of this DSC Configuration are self-explanatory as long as you keep in mind the example environment that is being configured.

Configuring the iSCSI Initiator

A DSC configuration for each of the iSCSI Initiators that will connect to the iSCSI Server Target requires the following steps to be performed in the DSC Resource:

  1. Start the Microsoft iSCSI Initiator Service service (MSiSCSI).
  2. Use the WaitForAny WMF 5.0 DSC Resource to wait for the iSCSI Server Target to be created (optional).
  3. Connect the iSCSI Initiator to the iSCSI Server Target and optionally register it with an iSNS Server.

Here is the DSC Configuration for CLUS1.CONTOSO.COM (the configuration for the other nodes would be similar except with different InitiatorPortalAddress values):

Configuration ISCSI_INITIATOR
{
Import-DscResource -ModuleName PSDesiredStateConfiguration
Import-DscResource -ModuleName xPSDesiredStateConfiguration
Import-DscResource -ModuleName ciSCSI
Node 'CLUS1' {
# Step 1 - Start the Microsoft iSCSI Initiator Service service (MSiSCSI)
Service iSCSIService
{
Name = 'MSiSCSI'
StartupType = 'Automatic'
State = 'Running'
}
# Step 2 - Use the WaitForAny WMF 5.0 DSC Resource to wait for the iSCSI Server Target to be created (optional).
WaitForAny WaitForiSCSIServerTarget
{
ResourceName = "[ciSCSIServerTarget]ClusterServerTarget"
NodeName = 'FS1'
RetryIntervalSec = 30
RetryCount = 30
DependsOn = "[Service]iSCSIService"
}
# Step 3 - Connect the iSCSI Initiator to the iSCSI Server Target and optionally register it with an iSNS Server.
ciSCSIInitiator iSCSIInitiator
{
Ensure = 'Present'
NodeAddress = 'iqn.1991-05.com.microsoft:fs1-fs1-server-target-target'
TargetPortalAddress = '192.168.129.10'
InitiatorPortalAddress = '192.168.129.24'
IsPersistent = $true
iSNSServer = 'isns1.contoso.com'
DependsOn = "[WaitForAny]WaitForiSCSIServerTarget"
} # End of ciSCSITarget Resource
}
}

Important: We need to make sure the NodeAddress is set to the the Target IQN from the iSCSI Server Target – in this case ‘iqn.1991-05.com.microsoft:FS1-FS1-Server-Target-Target’.

It is also recommended that you use IP Addresses for the TargetPortalAddress and InitiatorPortalAddress parameters rather than server names, as this will force the iSCSI traffic to use the appropriate network adapter.

The components of this DSC Configuration are self-explanatory as long as you keep in mind the example environment that is being configured.

iSNS Server Configuration

There are a few things to keep in mind when you have your iSCSI DSC Configurations registering with an iSNS Server:

  1. The Default Domain on the iSNS Server should have been created.
  2. If the iSNS Server is not available or contactable by the iSCSI Server Target or Initiator when the DSC Configuration is applied the DSC configuration will not throw an error, but the iSNS Server Address will not be set. However, next time the DSC configuration is applied by the LCM it will try again (and again the next time etc).

Using iSNS Server is completely optional and is mostly used in larger environments with more than twenty iSCSI Server Targets and where the Initiators will be connected to the iSCSI Server Targets manually or where DSC can’t be used on the iSCSI Server Targets.

That is all there is to using this resource to configure a Windows Server 2012 iSCSI SAN using DSC.

Note: I have submitted this DSC Resource to be included in the Microsoft Community DSC Resources project. If it is accepted then the name of the DSC Resource will change from ciSCSI to iSCSI. The resource hasn’t yet been reviewed and I’m not aware of an ETA for it. The old ‘c’ and ‘x’ nomenclature used by DSC Resources is being phased out.

If you need some additional guidance or other specific examples, please feel free to drop a comment on this blog post (or the GitHub repository) and I’ll do my best to help you out.