PowerShell and Server Core

 By Richard Siddaway, PowerShell MVP and author on a number of books on PowerShell, Active Directory and Windows Administration. He is a regular speaker at the PowerShell Summit and user groups, and blogs regularly on his PowerShell blog.

Sometimes, when you have multiple problems you can use one problem to help you solve the other. This situation exists with PowerShell and Server Core. Server core is a version of Windows Server (now the default installation option) that doesn’t have a GUI – a fact that seems to put a lot of IT Pros off. PowerShell is a scripting language and automation engine for administering Windows systems – a lot of IT pros don’t feel comfortable using PowerShell, if they’ve even looked at it. By learning how to use PowerShell effectively you can manage Server Core instances as easily as GUI versions of Windows.

Skill up for Windows Server 2016

This knowledge will become especially useful with the introduction of Nano Server and Windows Containers in Windows Server 2016. Nano server has an even smaller footprint than Server Core and you can’t log on to it – all administration has to be performed remotely. Windows Containers are a lightweight virtualisation platform that again, are managed remotely. Knowing how to manage systems remotely will be a big advantage.

Origins

Server core was introduced in Windows Server 2008. That version didn’t have PowerShell and had to be managed by scripts written in VBScript. PowerShell appeared in Windows Server 2008 R2 Server Core, but it wasn’t until Windows Server 2012 and Windows Server 2012 R2 that managing Server Core became really easy because of the explosion in the number and type of cmdlets available. Things that required extensive scripting became simple one line commands.

For the rest of the article I’m going to work through creating, configuring and managing a Windows Server 2012 R2 Server Core instance. The code should all work the same on Windows Server 2012. For ease of use I really recommend using the 2012 versions rather than the 2008 versions.

Contents

Creating a Server Core instance

Configuring your Server Core Administering the new machine

Adding Windows features to your Server Core

 

Creating a Server Core instance

First off we need to create a Server Core instance. Time to create a virtual machine. If you have an automated process for creating VMs then feel free to use that. In my lab I create VMs manually because I’m not creating them often enough to warrant the investment in further automation – though I think that will need to change soon.

Creating a new VM

You create a new VM using the Hyper-V cmdlets – you didn’t think I was going to let you use the GUI did you? Feel free to change the variables, paths and other data to match your environment.

$newmachine = 'W12R2SC01'

 

$path = 'C:\VirtualMachines'

New-Item -Path $path -Name $newmachine -ItemType Directory

 

$vmfolder  = Join-Path -Path $path -ChildPath $newmachine

$vdiskname = Join-Path -Path $vmfolder -ChildPath "$newmachine.vhdx"

Define the new machine name – I build the version information into the name for my lab, but feel free to use your own convention. My VMs are stored in C:\VirtualMachines so define the path to that and create a folder for the new machine. Use the new machine name, path and folder information to create variables holding the folder path and the virtual disk name.

The other piece of information you need to create a VM is the virtual switch:

$vswitch = Get-VMSwitch | select -First 1 -ExpandProperty Name

Get the list of switches, select the first one and then expand the Name property. This gives you the value of the name as a string that you can store in a variable – rather than an object where the Name is a property. This works in my lab where I just have two virtual switches. You may need to select the relevant switch differently. There is a -Skip parameter in Select-Object that helps refine your choices.

Now you can create a VM:

New-VM -Name $newmachine -MemoryStartupBytes 1024MB -Path $vmfolder -BootDevice CD -NewVHDPath $vdiskname -NewVHDSizeBytes 80GB -SwitchName $vswitch -Generation 2

Define the new Generation 2 VM using the name, path and disk name already created. The startup memory needs to be at least 1GB. Hyper-V thinks in MB for this so I’ve used 1024MB (PowerShell understands KB, MB, GB, TB and PB). The VM will boot from the DVD and use the switch defined earlier. An 80GB virtual disk will be created.

Hyper-V automatically adds the network adapter to use the virtual switch you defined and the DVD drive – you don’t need to define anything:

Set-VM -Name $newmachine -DynamicMemory -AutomaticStartAction Nothing -AutomaticStopAction ShutDown

After the machine is created you can set it to use dynamic memory and set the automatic start and stop actions:

Set-VMDvdDrive -VMName $newmachine -Path 'C:\Source\Windows 2012 R2 updates\en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso'

 

Start-VM -Name $newmachine

The final steps

The final steps are to attach the .iso file with the install media and to start the VM. The VM will boot off the DVD drive and start the install process. Go through the normal install process – Server Core is the default installation – and when it’s finished, set the Administrator account password when prompted.

I have a DHCP server in my lab so the new machine will pick up an IP address and other network configuration information through DHCP. That means it’s on the network and I can get to it.

You’ll then be logged onto the new Server Core machine and get something like the below. In this case I’m viewing the console provided by double clicking the VM in the Hyper-V console.

 

Configuring your Server Core

You can switch from cmd.exe to PowerShell by simply typing PowerShell at the prompt and pressing enter. You could do all of the subsequent configuration steps through that console, but that’s no fun. The idea is to practice using remote access in order to be ready for Nano server and Windows containers.

There are a number of configuration steps needed to get the machine ready:

  1. Eject the media from the DVD drive
  2. Rename the machine (Windows installer creates a name – wish it asked for the name like older versions of Windows)
  3. Join the machine to the domain
  4. Rename the network adapter
  5. Set static IP address and other networking tasks
  6. Add a second adapter and set its name
  7. Configure RDP access
  8. Activate Windows
  9. Possibly add, and configure, some Windows features

Ejecting the media from the DVD is necessary so that you don’t accidently start a new install:

Get-VMDvdDrive -VMName $newmachine | Format-List

DvdMediaType       : ISO

Path               : C:\Source\Windows 2012 R2 updates\en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso

ControllerType     : SCSI

ControllerNumber   : 0

ControllerLocation : 1

Name               : DVD Drive on SCSI controller number 0 at location 1

PoolName           : Primordial

ComputerName       : SERVER02

Id                 : Microsoft:0F9C9B32-78D4-43B5-B3C2-D7205926A7F9\D797832C-7F50-4AB3-990B-593DB1B82D3F\0\1\D

IsDeleted          : False

VMId               : 0f9c9b32-78d4-43b5-b3c2-d7205926a7f9

VMName             : W12R2SC01

VMSnapshotId       : 00000000-0000-0000-0000-000000000000

VMSnapshotName     :

Key                :

You can use Set-VMDvdDrive to remove the iso – just set the path to NULL (empty in effect).

Get-VMDvdDrive -VMName $newmachine | Set-VMDvdDrive -Path $null

 

Administering the new machine

You have a number of techniques to administer the new machine:

  • PowerShell remoting
  • CIM cmdlets (using WSMAN same as PowerShell remoting)
  • WMI cmdlets using DCOM (CIM cmdlets can also use DCOM)
  • Individual cmdlet remoting capabilities – maybe WSMAN, DCOM, RPC or other protocols.

For this exercise, PowerShell remoting is the optimum technique. Interrogating the DHCP server provides the IP address and (temporary) host name of the new machine:

Get-DhcpServerv4Lease -ScopeId '10.10.54.0'

 

IPAddress       : 10.10.54.3

ScopeId         : 10.10.54.0

Description     :

ClientId        : 00-15-5d-36-c9-35

HostName        : WIN-OUK5J7H9VIE

ClientType      : Dhcp

AddressState    : Active

DnsRR           : PTR

DnsRegistration : Complete

LeaseExpiryTime : 19/01/2016 23:04:10

NapCapable      : False

NapStatus       : FullAccess

ProbationEnds   :

PolicyName      :

ServerIP        : 10.10.54.201

You need to create a credential object based on the local Administrator account of the new machine:

$cred = Get-Credential -Credential 10.10.54.3\Administrator

Connecting to the new machine

Attempting to connect to the new machine using its name fails because it’s not in DNS. Yes I deliberately set it up that way, and if it was in DNS it would still fail with the error message below. If you try to use the IP Address you also get this error message:

Invoke-Command -ComputerName '10.10.54.3' -ScriptBlock {Get-NetAdapter} -Credential $cred 

 

[10.10.54.3] Connecting to remote server 10.10.54.3 failed with the following error message : The WinRM client cannot process the request. Default authentication may be used with an IP address under the following conditions: the transport is HTTPS or the destination is in the TrustedHosts list, and explicit credentials are provided. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. For more information on how to set TrustedHosts run the following command: winrm help config. For more information, see the

about_Remote_Troubleshooting Help topic.

    + CategoryInfo          : OpenError: (10.10.54.3:String) [], PSRemotingTransportException

    + FullyQualifiedErrorId : CannotUseIPAddress,PSSessionStateBroken

The easiest way to resolve this is to add the machine to the trusted hosts list on a temporary basis. Some purists may object to this approach, but it gets the job done. The danger with using the trusted host list is that the machine authentication is effectively ignored. As it’s a known machine and only used until we get the machine into the domain, I regard it as a legitimate technique.

To add the IP address of the new machine to the trusted hosts list:

Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value '10.10.54.3' -Force

Now you can connect.

Joining the domain

Joining to the domain and renaming the computer is a one stop operation achieved by using the Add-Computer cmdlet.

First create a credential for the account that can join the machine to the domain:

$domcred = Get-Credential -Credential manticore\richard

Then create a script block with the details Add-Computer needs:

$sb = {

param ($domcred, $newmachine)

Add-Computer -Credential $domcred -DomainName manticore.org  -OUPath "OU=Servers,DC=Manticore,DC=org" -NewName $newmachine  -Force -Restart

}

The important part to note is that parameters are passed into the script block defining the domain join credential and the name of the new machine. These variables were defined on the local machine, but the remote machine won’t know anything about them so you have to pass them into the script block.

Lastly, run the script block against the remote machine passing the required variables to the script block:

Invoke-Command -ComputerName '10.10.54.3' -ScriptBlock $sb -Credential $cred -ArgumentList $domcred, $newmachine

You can check that the machine joined the domain and has been renamed by using the Active Directory cmdlets.

Get-ADComputer -Identity $newmachine

Removing from the trusted hosts list

Now that the machine in the domain has a sensible name you can remove it from the trusted hosts list:

Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value '' -Force

Rename the network adapter and set a static IP address

The next items on our to-do list are to rename the network adapter (default name in Hyper-V is ‘Ethernet’) and set a static IP address:

$sb = {

 Get-NetAdapter -Name Ethernet | Rename-NetAdapter -NewName 'LAN'

 $index = Get-NetAdapter -Name LAN | select -ExpandProperty ifIndex

 

 Set-DnsClientServerAddress -InterfaceIndex $index -ServerAddresses ‘10.10.54.201’

 

 Set-DnsClient -InterfaceIndex $index -ConnectionSpecificSuffix ‘manticore.org’

 

 New-NetIPAddress -InterfaceIndex $index -AddressFamily IPv4 -IPAddress '10.10.54.61' -PrefixLength 24

}

 

Invoke-Command -ComputerName $newmachine -ScriptBlock $sb

The script block finds the current network adapter and renames it to ‘LAN’. The interface index of that adapter is found and used to set the DNS server address, the DNS connection suffix and the IP address. Invoke-Command can use the new name of the machine and you won’t need any credentials, assuming your domain account can administer machines.

When you use Invoke-Command to run the script block you’ll get a message stating that the connection to the new machine has been interrupted. This happens because the IP address was changed so the WSMAN configuration at the remote machine was effectively broken.

By default, PowerShell will retry the connection for up to 4 minutes. Press Ctrl-C to terminate the command. The new machine can be shut down at this point (using Stop-VM) as a new network adapter has to be added. You may have to reboot a machine after the IP address through which you’re connecting is changed because sometimes remoting can get a bit stuck when you perform an action that leaves a broken session.

After closing down the machine add the network adapter:

$vm = Get-VM -Name $newmachine

$switch2 = Get-VMSwitch | select -Last 1 -ExpandProperty Name

Add-VMNetworkAdapter -VM $vm -SwitchName $switch2 -Name 'Wifi'

This is similar to the process when we created the VM. The relevant switch is found and Add-VMNetworkAdapter is used to add the adapter.

Switching the boot order to the hard disk

While the machine is switched off it’s a good time to switch the boot order to the hard disk:

Set-VMFirmware -VM $vm -FirstBootDevice (Get-VMHardDiskDrive -VM $vm)

And the machine can be started.

Start-VM -VM $vm

Creating a PowerShell remoting session

There are a few things left to do. Creating a PowerShell remoting session at this stage is a good idea as the overhead of building, and rebuilding, connections is taken away. The network configuration is stable at this point so a remoting session is a good investment:

$sess = New-PSSession -ComputerName $newmachine

Now, we can rename our new adapter:

Invoke-Command -Session $sess -ScriptBlock {Get-NetAdapter -Name Ethernet | Rename-NetAdapter -NewName 'Wifi'}

Remember that Hyper-V uses ‘Ethernet’ as the default name and as we’ve already renamed the original adapter, the new one can use that name. Notice that the -Session parameter is used rather than -ComputerName to make Invoke-Commanduse the remoting session that already exists.

Enable RDP access

If you’re feeling kind at this stage you can enable RDP access for those admins that don’t use PowerShell.

$sb = {

[uint32]$atsc = 1

[uint32]$mfe = 1

 

$ts = Get-CimInstance -Namespace Root\CIMv2\TerminalServices -ClassName Win32_TerminalServiceSetting

 

Invoke-CimMethod -InputObject $ts -MethodName SetAllowTSConnections -Arguments @{AllowTSConnections = $atsc; ModifyFirewallException = $mfe}

 

## set firewall rules if needed

Enable-NetFirewallRule -Name RemoteDesktop-UserMode-In-TCP, RemoteDesktop-UserMode-In-UDP

 

}

Invoke-Command -Session $sess -ScriptBlock $sb

The TerminalServices CIM (WMI) class has a SetAllowTSConnections method which turns on RDP. Notice that the 2 variables defined at the beginning of the script block are unsigned integers. The appropriate firewall rules are enabled to allow RDP. If you turn off the Windows firewall you can remove that line.

Firewall settings

Speaking of the firewall there a few other rules it would be worth setting:

$sb = {

Set-NetFirewallRule -DisplayGroup 'File and Printer Sharing' -Enabled True  -Profile Domain

 

Set-NetFirewallRule -DisplayGroup 'Windows Management Instrumentation (WMI)' -Enabled True  -Profile Domain

 

Set-NetFirewallRule -DisplayGroup 'Remote Shutdown' -Enabled True  -Profile Domain

 

Set-NetFirewallRule -DisplayGroup 'Windows Remote Management' -Enabled True  -Profile Domain

}

Invoke-Command -Session $sess -ScriptBlock $sb

These rules will enable file sharing (can map to shares on the new machine), WMI remote management, Windows Remote Management and Remote Shutdown.

Activation testing

The second adapter I added has Internet connectivity so Windows should have automatically activated. The activation can be tested:

$sb = {

Get-CimInstance -ClassName SoftwareLicensingProduct | where PartialProductKey |

select Name, ApplicationId,

@{N='Status'; E={if ($_.LicenseStatus -eq 1){"Activated"}else{"NotActivated"}}}, LicenseStatus

}

Invoke-Command -Session $sess -ScriptBlock $sb

If you need to activate Windows then see page 353 of my PowerShell and WMI  book.

 

Adding Windows features to your Server Core

Your server core machine is now at the stage where you can add the Windows features you need. If you view the features on the remote machine:

Invoke-Command -Session $sess -ScriptBlock {Get-WindowsFeature}

You’ll see that a number of features such as Windows Deployment Services have been removed. You can get a better view of the available features by only passing those features that are available:

Invoke-Command -Session $sess -ScriptBlock {Get-WindowsFeature | where InstallState -eq 'Available'}

If you want to see what’s installed, use:

Invoke-Command -Session $sess -ScriptBlock {Get-WindowsFeature | where InstallState -eq 'Installed'}

You can use Add-WindowsFeatureto install the features you want. When you’ve finished configuring the machine don’t forget to remove the remoting session:

Get-PSSession -ComputerName $newmachine | Remove-PSSession

This may seem like a lot of steps but I’ve broken it down in logical groups to make the explanation easier. In PowerShell 4.0 and 5.0 you could use Desired State Configuration to perform some, if not all, of these tasks but that’s an article for another day.

Resources

Why you need to learn PowerShell Starting your PowerShell journey