Monthly Archives: March 2017

How to tell if you’re running on Windows Server Core

I have a bunch of scripts I use when I'm building a lab to install "stuff" (that's the Technical Term we IT Professionals use) that I need to manage and work with a virtual machine. Now, when I build from a SysPrep'd image, that's not an issue, but if I have to build from an ISO, I want to automate the process as much as possible. So I have a couple of Setup scripts I run that install gVim, HyperSnap (my screen capture tool), and various other things.

As I was building a new lab this week, I realized that those scripts were all designed to deal with full GUI installations, and had no provisions for not installing applications that make no sense and can't work when there's only a Server Core installation. So, time to find out how I can tell if I'm running as Server Core, obviously. A bit of poking around, and I came up with the following:

$regKey = "hklm:/software/microsoft/windows nt/currentversion"
$Core = (Get-ItemProperty $regKey).InstallationType -eq "Server Core"

(You could do that as a single line, obviously, but I broke it up to make it easier to see on the page. )

The result is stored as a Boolean value in $Core, and I can now branch my installation decisions based on the value of $Core. (Note there ARE other ways to determine whether you're running on Server Core, but they appear to all be programmatic ones not well suited to the avowedly non-programmer IT system administrator types like me. )

Building a Lab in Hyper-V with PowerShell, Part 5

Deploying a DHCP Server

Now that you have your forest and domain installed, including DNS, the next step to setting up a lab is the DHCP server.  Start by creating a new VM for the DHCP server, trey-dhcp-03. (For details on how to create a VM with PowerShell, see Building a Lab in Hyper-V Part 2 and Part 3. ) There's no particular need to make this a GUI installation, so build it as a Server Core installation. We'll do the configuration all in PowerShell anyway.

 

Next, Install the DHCP role, add the local groups required, and authorize it in Active Directory. (Do note the slightly different server name when you go to do that, please, and you don't need or want to promote this server to a domain controller. )

 

Now, as you'll remember from earlier posts in this series, I configure all my VMs with known MAC addresses by first defining the range and then requiring a MAC address final pair parameter to New-myVM.ps1This allows me to now configure a set of reservations for each VM in the lab, simplifying connections and making it a lot easier for me to keep track what is where.

 

Assuming by now you have installed the DHCP role and authorized it in Active Directory, the next step is to set up your IPv4 and IPv6 ranges. We do that by first adding a scope, then setting exclusion ranges and finally scope options. For IPv4, this is three commands:

Add-DhcpServerv4Scope -Name "Trey-Default" `
                      -ComputerName "trey-dhcp-03" `
                      -Description "Default IPv4 Scope for Lab" `
                      -StartRange "192.168.10.1" `
                      -EndRange   "192.168.10.220" `
                      -SubNetMask "255.255.255.0" `
                      -State Active `
                      -Type DHCP `
                      -PassThru

Add-DhcpServerv4ExclusionRange -ScopeID "192.168.10.0" `
                               -ComputerName "trey-dhcp-03" `
                               -StartRange "192.168.10.1" `
                               -EndRange   "192.168.10.20" `
                               -PassThru

Set-DhcpServerv4OptionValue -ScopeID 192.168.10.0 `
                            -ComputerName "trey-dhcp-03" `
                            -DnsDomain "TreyResearch.net" `
                            -DnsServer "192.168.10.2" `
                            -Router "192.168.10.1" `
                            -PassThru

Now,  the same process for IPv6, though I usually do NOT create IPv6 reservations, but do want to set some default values.

Add-DhcpServerv6Scope -Name "Trey-IPv6-Default" `
                      -ComputerName "trey-dhcp-03" `
                      -Description "Default IPv6 Scope for Lab" `
                      -Prefix 2001:db8:0:10:: `
                      -State Active `
                      -PassThru

Add-DhcpServerv6ExclusionRange –ComputerName trey-dhcp-03 `
                               -Prefix 2001:db8:0:10:: `
                               -StartRange 2001:db8:0:10::1 `
                               -EndRange   2001:db8:0:10::20 `
                               -PassThru

Set-DhcpServerv6OptionValue -Prefix 2001:db8:0:10:: `
                            -ComputerName "trey-dhcp-03" `
                            -DnsServer 2001:db8:0:10::2 `
                            -DomainSearchList "TreyResearch.net" `
                            -PassThru

Now, create a CSV file with Names,MAC addresses(ClientID), and IPv4 Addresses. You can use your favourite plain text editor (mine is gVim), or Excel to create the CSV file. My lab has the following for the 192.168.10.xxx range of IP addresses:

Name,ClientID,IPAddress
trey-edge-01,00-15-5D-32-0A-01,192.168.10.1
trey-dc-02,00-15-5D-32-0A-02,192.168.10.2
trey-dhcp-03,00-15-5D-32-0A-03,192.168.10.3
trey-dc-04,00-15-5D-32-0A-04,192.168.10.4
trey-srv-05,00-15-5D-32-0A-05,192.168.10.5
trey-wds-11,00-15-5D-32-0A-0B,192.168.10.11
Trey-Srv-12,00-15-5D-32-0A-0C,192.168.10.12
Trey-Srv-13,00-15-5D-32-0A-0D,192.168.10.13
Trey-Srv-14,00-15-5D-32-0A-0E,192.168.10.14
Trey-Srv-15,00-15-5D-32-0A-0F,192.168.10.15
Trey-Srv-16,00-15-5D-32-0A-10,192.168.10.16
Trey-client-21,00-15-5D-32-0A-15,192.168.10.21
Trey-client-22,00-15-5D-32-0A-16,192.168.10.22
Trey-client-23,00-15-5D-32-0A-17,192.168.10.23
Trey-client-24,00-15-5D-32-0A-18,192.168.10.24
Trey-client-25,00-15-5D-32-0A-19,192.168.10.25

Save the CSV file as "TreyDHCP.csv". Now, to create the reservations, first read in the CSV file with:

$TreyDHCP = Import-CSV TreyDHCP.csv

Then, create the IPv4 reservations with a simple ForEach loop:

ForEach ($addr in $TreyDHCP ) {
   $ErrorActionPreference = "Continue"
   Add-DhcpServerv4Reservation -ScopeID   192.168.10.0 `
                               -Name      $addr.Name `
                               -ClientID  $addr.ClientID `
                               -IPAddress $addr.IPAddress `
                               -PassThru
}

If you run multiple NICs on your lab environment, you'll want to repeat all of the above for the second range of IP addresses.

So, here's the whole thing in a script that supports running remotely.

<#
.Synopsis
Install and configure DHCP for the TreyResearch.net lab environment
.Description
The New-TreyDHCP script installs and configures the DHCP environment for the TreyResearch.net
lab environment. It assumes a DHCP server "trey-dhcp-03" has already been created, but accepts
a parameter to change the server name. 
The script reads a CSV file with the machine names, MAC addresses (ClientIDs), and IPv4
addresses that the that the network will use and then creates IPv4 DHCP reservations for those
machines.
.Example
New-TreyDHCP.ps1
Reads in a list of DHCP addresses from TreyDHCP.csv and configures trey-dhcp-03 as a DHCP
server with those addresses. 
.Example
New-TreyDHCP.ps1 -ComputerName Trey-core-03 -Path c:\temp\dhcp.csv
Reads in a list of DHCP addresses from c:\temp\dhcp.csv and configures the server
Trey-core-03 as a DHCP server with those address reservations. 
.Parameter ComputerName
The server to install and configure DHCP on. Default value is trey-dhcp-03
.Parameter Path
The path to a CSV file with the machine names, client IDs, and IPv4 addresses to configure
DHCP reservations for. The default value is .\TreyDHCP.csv. 
.Inputs
[string]
[string]
.Notes
    Author: Charlie Russel
 Copyright: 2017 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 25 March, 2014 (cpr)
   ModHist: 14 March, 2017 (cpr) Added ComputerName parameter and man page
          : 
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$False)]
     [alias("server")]
     [string]
     $ComputerName = 'trey-dhcp-03',
     [Parameter(Mandatory=$False)]
     [Alias("filename")]
     [string]
     $Path = '.\TreyDHCP.csv'
     )

if ( (Get-WindowsFeature -Name DHCP -ComputerName $ComputerName) -ne "Installed" ) {
  Install-WindowsFeature -Name DHCP -ComputerName $ComputerName -IncludeManagementTools 
}

if (Test-Path $Path ) { 
   $TreyDHCP = Import-CSV $Path 
} else {
   Throw "This script requires an input CSV file with the DHCP Reservations in it."
}

# Find out if the DHCP Server is already authorized. If it is, 
# we assume all the rest of this is done. 
If ( (Get-DhcpServerInDC).DnsName -match $ComputerName ) {
   $IsAuth = $True 
} else {
   $IsAuth = $False 
   $DnsName = $ComputerName + ".TreyResearch.net"
}

# If the server isn't authorized, then nothing is set yet, so set up 
# our DHCP server. 
if (! $IsAuth) {
   Add-DhcpServerInDC -DnsName $DnsName -PassThru
   # Create local groups for DHCP
   # The WinNT in the following IS CASE SENSITIVE
   $connection = [ADSI]"WinNT://$ComputerName"
   $lGroup = $connection.Create("Group","DHCP Administrators")
   $lGroup.SetInfo()
   $lGroup = $connection.Create("Group","DHCP Users")
   $lGroup.SetInfo()
   Add-DhcpServerv4Scope -Name "Trey-Default" `
                         -Description "Default IPv4 Scope for TreyResearch Lab" `
                         -StartRange "192.168.10.1" `
                         -EndRange   "192.168.10.220" `
                         -SubNetMask "255.255.255.0" `
                         -State Active `
                         -Type DHCP `
                         -ComputerName $ComputerName `
                         -PassThru
   Add-DhcpServerv4ExclusionRange -ScopeID "192.168.10.0" `
                                  -StartRange "192.168.10.1" `
                                  -EndRange   "192.168.10.20" `
                                  -ComputerName $ComputerName `
                                  -PassThru
   Set-DhcpServerv4OptionValue -ScopeID 192.168.10.0 `
                               -DnsDomain "TreyResearch.net" `
                               -DnsServer "192.168.10.2" `
                               -Router "192.168.10.1" `
                               -ComputerName $ComputerName `
                               -PassThru
   Add-DhcpServerv6Scope -Name "Trey-IPv6-Default" `
                         -Description "Default IPv6 Scope for TreyResearch Lab" `
                         -Prefix 2001:db8:0:10:: `
                         -State Active `
                         -ComputerName $ComputerName `
                         -PassThru
   Add-DhcpServerv6ExclusionRange -Prefix 2001:db8:0:10:: `
                                  -StartRange 2001:db8:0:10::1 `
                                  -EndRange   2001:db8:0:10::20 `
                                  -ComputerName $ComputerName `
                                  -PassThru
   Set-DhcpServerv6OptionValue -Prefix 2001:db8:0:10:: `
                               -DnsServer 2001:db8:0:10::2 `
                               -DomainSearchList "TreyResearch.net" `
                               -ComputerName $ComputerName `
                               -PassThru
}


ForEach ($addr in $TreyDHCP ) {
   $ErrorActionPreference = "Continue"
   Add-DhcpServerv4Reservation -ScopeID 192.168.10.0 `
                               -Name $addr.Name `
                               -ClientID $addr.ClientID `
                               -IPAddress $addr.IPAddress `
                               -ComputerName $ComputerName `
                               -PassThru
}

I hope you find this script useful, and I'd love to hear comments, suggestions for improvements, or bug reports as appropriate. As always, if you use this script as the basis for your own work, please respect my copyright and provide appropriate attribution.

PowerShell: Rename an Active Directory User

This came up at work the other day. Another admin had attempted to rename an AD User account and it had only partially gotten renamed -- the SAM Account, Name and Display name were all correct, but the old user name was still showing up in a couple of places, including the login screen. The user was not happy, so I was asked to fix it, and provide a script that would handle it correctly. I poked around a bit and found the issue - even if you set all of the obvious properties correctly (and the other admin had missed UPN), it still won't show correctly on that logon screen -- you need to actually rename the AD object itself. So, after I fixed the problem user's account, I wrote up a script to solve the problem for the next time. I chose to use a CSV file as the input, but you could easily re-work this to work off either a CSV file or a set of command-line parameters. But honestly, I don't ever want to have to enter that many command-line parameters for a simple script. Especially if I have more than one to change.

 

The script uses Get-ADUser with the old name, then pipes it to Set-ADUser, and finally pipes it to Rename-ADObject to finish the process.  I even gave it basic help. :)

<#
.Synopsis
Renames the Active Directory users
.Description
Rename-myADUser reads a CSV file to identify an array of users. The users are then 
renamed to the new name in Active Directory.
.Example
Rename-myADUser
Renames the AD Accounts of the users in the default "ADUsers.csv" source file
.Example
Rename-myADUser -Path "C:\temp\ChangedUsers.txt"
Renames the AD accounts of the users listed in the file C:\temp\ChangedUsers.txt"
.Parameter Path
The path to the input CSV file of format:
OldSam,NewName,GivenName,Surname,DisplayName,SAMAccountName,UserPrincipalName,EmailAddress

The default value is ".\ADUsers.csv".  
.Inputs
[string]
.Notes
    Author: Charlie Russel
 Copyright: 2017 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 03/09/2017 (cpr)
   ModHist: 
          :
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$False,Position=0)]
     [string]
     $Path = ".\ADUsers.csv" 
     )

$ADUsers = @()
If (Test-Path $Path ) {
   $ADUsers = Import-CSV $Path
} else { 
   Throw  "This script requires a CSV file with user names and properties."
}
$PDC = (Get-ADDomain).PDCEmulator
Write-Verbose "The PDC Emulator has been identified as $PDC"
Write-Verbose " "

ForEach ($User in $ADUsers ) {
   Write-Verbose "Modifying $user.OldSam to $user.NewName" 
   Sleep 3
   Get-ADUser -Identity $User.OldSam -Properties * | `
   Set-ADUser -Server $PDC `
              -DisplayName $user.DisplayName `
              -EmailAddress $User.EmailAddress `
              -SamAccountName $User.SamAccountName `
              -GivenName $User.GivenName `
              -Surname $User.Surname `
              -UserPrincipalName $user.UserPrincipalName `
              -PassThru | `
   Rename-ADObject -NewName $user.NewName -Server $PDC -PassThru
}

 

Building a Lab in Hyper-V with PowerShell, Part 4

Creating a new forest

In the previous sections of this series, I've covered how to build VMs using PowerShell, but labs aren't much good if they don't actually have any structure. So, let's create a new forest and domain to manage our labs. I'm going to assume for this post that you've gotten started already and created a new Windows Server 2012R2 or Windows Server 2016 virtual machine. For this, it can be a graphical install or a Server Core installation and either Server Standard or Datacenter. Since we're going to be using only PowerShell to create the forest, there's no need for a GUI.

The things we'll need to have identified before we start are:

  • Server IP address
  • Server name
  • DNS namespace for the root domain of the forest
  • Domain name for the root domain of the forest
  • DNS Server type (AD-integrated or standalone)

Set Server IP Address

We need set our server to a fixed IP address. While not absolutely required, I think it's a really bad idea to not do this. And, since our lab doesn't yet have DHCP in it, you need to anyway. (We'll add a DHCP server in the next installment. )

To configure the network adapter for a static IP address, I need to know either the interface alias (name) or the interface index. To get those, use Get-NetAdapter from a PowerShell window. (Note: if you're doing this on a new Windows Server Core installation, you can open a PowerShell window with Start PowerShell.exe at the command prompt. To start a PowerShell window automatically for this user, at logon, see my May post. )

Get-NetAdapter | Format-Table -AutoSize Name,Status,IFIndex,MacAddress

Name       Status ifIndex MacAddress
----       ------ ------- ----------
Ethernet 2 Up           3 00-15-5D-32-0A-02
Ethernet   Up           5 00-15-5D-32-CE-02

Which tells us that the DC has two network adapters, and the one that is on the Local-10 switch (from New-myVM.ps1) is at an ifIndex of 3, while the one on the "199 Network" switch has an ifIndex of 5. Now, we'll set the static IP addresses for these two adapters. First, the NIC on Local-10:

# Set IPv4
$NIC2 = Get-NetAdapter -ifIndex 3
$NIC2 | Set-NetIPInterface -DHCP Disabled
$NIC2 | New-NetIPAddress -AddressFamily  IPv4 `
                         -IPAddress      192.168.10.2 `
                         -PrefixLength   24 `
                         -Type Unicast `
                         -DefaultGateway 192.168.10.1
# Set IPv6
$NIC2 | New-NetIPAddress -AddressFamily  IPv6 `
                         -IPAddress      2001:db8:0:10::2 `
                         -PrefixLength   64 `
                         -Type Unicast `
                         -DefaultGateway 2001:db8:0:10::1

# Set DNS Server Addresses to self
Set-DnsClientServerAddress -InterfaceIndex  $NIC2.ifIndex `
                           -ServerAddresses 192.168.10.2,2001:db8:0:10::2

#Now, for the 199 Network, which I use for internal communications between lab hosts, I want to set a pure IPv4 address with no IPv6, so instead of setting an IPv6 address for the NIC, I'll disable it with Disable-NetAdapterBinding.

$NIC = Get-NetAdapter -ifIndex 5

# Disable IPv6
Disable-NetAdapterBinding -Name $NIC.Name -ComponentID ms_tcpip6

# Set IPv4 to 192.168.199.2
$NIC | Set-NetIPInterface -Dhcp Disabled
$NIC | New-NetIPAddress -AddressFamily IPv4 `
                        -IPAddress     192.168.199.2 `
                        -PrefixLength  24 `
                        -Type Unicast
# Set DNS to self
Set-DnsClientServerAddress -InterfaceIndex  $NIC.ifIndex `
                           -ServerAddresses 192.168.199.2

(Note: Set-NetAdapterBinding is not available on Windows 7/Server 2008 R2)

 

Set Server Name

Next, let's set the name of the server to match our naming conventions for this lab. We do this now, knowing it will force a reboot before we go any further.

Rename-Computer -NewName trey-dc-02 -Restart -Force

This will give the computer a new name and restart it.

 

Create Forest and Install AD-integrated DNS

Now that we have static IP addresses for our network adapters, and we've set the name of the server, we can go ahead and create our AD forest. First, we install Active Directory and update the PowerShell Help files with:

Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
Update-Help -SourcePath \\labhost\PSHelp

This installs the ActiveDirectory and ADDSDeployment modules that we'll need to create the forest. Now, we promote the server to be the first domain controller in the new forest. Before we do the actual install, we test to make sure we don't have any issues with Test-ADDSForestInstallation:

Test-ADDSForestInstallation `
         -DomainName 'TreyResearch.net' `
         -DomainNetBiosName 'TREYRESEARCH' `
         -DomainMode 6 `
         -ForestMode 6 `
         -NoDnsOnNetwork `
         -SafeModeAdministratorPassword (ConvertTo-SecureString `
                                                  -String 'P@ssw0rd' `
                                                  -AsPlainText `
                                                  -Force) `
         -NoRebootOnCompletion

Even though this is a brand new forest in an isolated lab setting, it's still a good practice to test before you actually deploy. And it doesn't cost all that much time or annoyance. I've included the SafeModeAdministratorPassword parameter to avoid the prompts for it. This is a lab, not real life. :) Also note that we're setting the forest and domain modes to Server2012R2. If you need earlier versions of domain controllers in your lab, you can set the mode accordingly.

The results of the test are as expected:

WARNING: Windows Server 2016 domain controllers have a default for the security setting named "Allow cryptography
algorithms compatible with Windows NT 4.0" that prevents weaker cryptography algorithms when establishing security
channel sessions.

For more information about this setting, see Knowledge Base article 942564
(http://go.microsoft.com/fwlink/?LinkId=104751).

WARNING: A delegation for this DNS server cannot be created because the authoritative parent zone cannot be found or it
 does not run Windows DNS server. If you are integrating with an existing DNS infrastructure, you should manually
create a delegation to this DNS server in the parent zone to ensure reliable name resolution from outside the domain
"TreyResearch.net". Otherwise, no action is required.


Message                          Context                                  RebootRequired  Status
-------                          -------                                  --------------  ------
Operation completed successfully Test.VerifyDcPromoCore.DCPromo.General.3          False Success

With that confirmation, we can go ahead and finish creating the forest and configuring DNS with the command:

Install-ADDSForest `
    -DomainName 'TreyResearch.net' `
    -DomainNetBiosName 'TREYRESEARCH' `
    -DomainMode 6 `
    -ForestMode 6 `
    -NoDnsOnNetwork `
    -SkipPreChecks `
    -SafeModeAdministratorPassword (ConvertTo-SecureString `
                                                  -String 'P@ssw0rd' `
                                                  -AsPlainText `
                                                  -Force) `
    -Force

You'll notice that the options here match our test pass, except I chose to bypass a second test. If you want to keep your SafeMode Administrator password private you can eliminate that parameter and you'll be prompted at the command line. When this finishes and the server has rebooted, you can log in with the TREYRESEARCH\Administrator account and the local Administrator password you had before you promoted the VM to be a domain controller.  This may or may not be the same as the SafeModeAdministratorPassword you set during the installation.